Initial contrib from blue24

This commit is contained in:
Marco Cawthorne 2021-07-30 12:05:41 +02:00
commit 8f453b9e1d
180 changed files with 38188 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.pk3

5
src/Makefile Normal file
View file

@ -0,0 +1,5 @@
CC=fteqcc
all:
cd client && $(MAKE)
cd server && $(MAKE)

4
src/client/Makefile Normal file
View file

@ -0,0 +1,4 @@
CC=fteqcc
all:
$(CC) progs.src

57
src/client/clientinfo.h Normal file
View file

@ -0,0 +1,57 @@
// NEW FILE
// Handles things that need to be persistent, or at least accessible, through
// the spectator and player - class changes.
// In English please?
// Available whether the player is in Spectator mode or ingame
// (collision, interacting seen by other players, etc.)
// WARNING: included before the gamemod's client defs.h file. Beware if anything
// important is missing from here (context) because of that.
typedef struct ClientInfo_s{
// Anyways, this object will store data related to weapon purchases temporarily
// (tempConfig).
// When a purchase is finallized, tempConfig will replace currentConfig
// (copy it over).
// But if "cancel" is used from the 1st layer of shop buttons after purchases
// (edits to tempConfig), it is all scrapped. tempConfig will be restored to
// currentCofig instead.
// currentConfig will be used to build the player's inventory at spawn.
weaponconfig_data_t weaponconfig_current;
weaponconfig_data_t weaponconfig_temp;
//TAGGG - CRITICAL!
// At some point, it may be wise to store these too, remove from the player class
// after enabling here.
// Also the flMoney var here is just "money" in player, for removal.
// And this will probably mean replacing 'clientstat(... STAT_MONEY)' with something
// that sends a message to the connected client (SVC_CGAMEPACKET) anytime a
// persistent money value (a serverside clientdata?) is changed. This avoids
// saving to the player/spectator entity, which may be odd memory to use when both
// player and spectator need to refer to how much money the 'client' has, despite
// being different entities.
// Or maybe it all still works somehow with 0 side-effects.
// -----------------------------------------------------------
/*
// How much money does this player/spectator have? Can't spend more than this.
// A var for slots would be needed if this varried between players, number of slots
// used is for after having spawned (player-only) so it doesn't need to be here.
float flMoney;
// Provided for quick reference. How many slots does the current loadout take?
// There is a record of how much money has been spent, similar to config, but that
// isn't too important.
// The attached "money" (variable) is much more important (only pay attention to it
// serverside, fetch it from clientside: getstati(...) or something.
int iTotalSlots;
int iTotalPrice;
*/
// -----------------------------------------------------------
} ClientInfo_t;
void ClientInfo_init(ClientInfo_t* arg_this);

13
src/client/clientinfo.qc Normal file
View file

@ -0,0 +1,13 @@
// pSeat and pSeatLocal can be trusted as they are, they're set appropriately in init.qc
// right before this call for each.
void ClientInfo_init(ClientInfo_t* arg_this){
// weaponconfig_current = spawn(weaponconfig_data_t);
// weaponconfig_temp = spawn(weaponconfig_data_t);
}

121
src/client/cmds.qc Normal file
View file

@ -0,0 +1,121 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
int
ClientGame_ConsoleCommand(void)
{
//printfline("ClientGame_ConsoleCommand: %s", argv(0));
switch(argv(0)) {
//TAGGG - is that ok?
case "+speedcustom":
pSeatLocal->iInputSpeed = TRUE;
break;
case "-speedcustom":
pSeatLocal->iInputSpeed = FALSE;
break;
//TAGGG - NEW ONES
case "getorigin":
sendevent("TS_Debug_getOrigin", "");
break;
case "getangle":
sendevent("TS_Debug_getAngle", "");
break;
case "firemode":
TS_playerChangeFiremode();
break;
case "useitems":
TS_playerUseItems();
break;
case "usepowerup":
TS_playerUsePowerup();
break;
// I think the plus is good here then?
// ...wait, not continuously reacted to in original TS. I have no idea.
// Oh well, catch the - anyway too to stop an annoying printout about that command being missing (release)
case "+alt1":
TS_playerCallAlt1();
break;
case "-alt1":
break;
//TAGGG - TODO: low priority.
// Let holding down the coldcock key (C by default) continuously use it, just like
// holding down primary fire to continuously fire. Something about setting a var
// to represent being held down or not for frame-by-frame logic to check for being on, then
// do the 'sendevent'. I think?
case "+alt2":
TS_playerCallAlt2();
//pSeat->m_iInputAlt2 = TRUE;
break;
case "-alt2":
//pSeat->m_iInputAlt2 = FALSE;
break;
case "dev_testorbituary":
//TAGGG - CRITICAL. Orbituary stuff...
//HUD_AddOrbituaries(player_localnum, TEAM_T, player_localnum, TEAM_CT, floor(random(1, CS_WEAPON_COUNT)), FALSE);
break;
// CUT.
/*
case "minimap":
pSeat.iMapExpand = 1 - pSeat.iMapExpand;
break;
case "overview_test":
pSeat.iOverview = 1 - pSeat.iOverview;
break;
*/
case "motd":
if(getplayerkeyvalue( player_localnum, "*spec" ) != "0"){
VGUI_ChangeScreen(VGUI_SCREEN::MOTD);
}
break;
case "buy":
//if(getstatf(STAT_BUYZONE) == TRUE) {
// VGUI_BuyMenu();
//}
//If we're in spectator mode we can do this
// no-screen check, not necessary probably: pSeatLocal->fVGUI_Display == VGUI_SCREEN::NONE &&
if(getplayerkeyvalue( player_localnum, "*spec" ) != "0"){
//we can show it!
VGUI_ChangeScreen(VGUI_SCREEN::BUYSIDEMENU);
}
break;
case "chooseteam":
//VGUI_ChooseTeam();
break;
//TAGGG - CRITICAL
// See CSEv_DropWeapon of src/server/weapons.qc, tie into that better
// somehow, may need to rely on Nuclide's own pickup type instead of our own.
case "drop":
//sendevent("WeaponDrop", "");
TS_playerDropWeapon(); //I do it fine.
break;
default:
return (0);
}
return (1);
}

101
src/client/defs.h Normal file
View file

@ -0,0 +1,101 @@
/*
* Copyright (c) 2016-2021 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "obituary.h"
#include "particles.h"
//TAGGG - VERY LITTLE USES "hud_color" RIGHT NOW!
// See inventory_logic_draw.qc for places that benefit from involving it, compare to behavior
// in original TS to see what it affects.
vector g_hud_color;
vector g_hudmins;
vector g_hudres;
/*
// Do we need these? Note the similarly named "g_hud_color" already above!
// Going to keep the latter two for compatability.
// Yes using old VGUI is kinda crappy but good for now.
vector vHUDColor; // Defined in HUD_Draw (HUD.c)
*/
vector vVGUIColor; // Defined in HUD_Draw (VGUI.c)
vector vCrossColor; // Defined in HUD_Draw (HUDCrosshair.c)
//TAGGG INCLUSION - new. Global var to store the current screen size. If it changes, the font should be re-loaded to fit it.
var vector Recorded_video_res;
//TAGGG - INCLUSION. eplaces several cases of FONT_CON to be able to draw larger text (well).
var float FONT_ARIAL;
var float FONT_ARIAL_TITLE;
var float FONT_ARIAL_STD; //stands for "standard" ya hooligans.
var float FONT_ARIAL_NUMSLASH;
var float FONT_ARIAL_SCOPEMAG;
// Be sure to keep this up to date with the font FONT_ARIAL_STD as it's loaded in _base/client/entry.c,
// notice this is always one less than the actual expected corresponding font.
const vector vButtonFontSize = [13, 13, 0];
const vector vFontSizeNumSlash = [15, 15, 0];
const vector vFontArialScopeMag = [16, 16, 0];
const vector vButtonSizStandard = [127, 19, 0];
const vector clrBtnDefault = [0, 0, 0];
const vector clrPaleBluePurple = [174.0f/255.0f, 152.0f/255.0f, 255.0f/255.0f];
//const vector clrFailedSlotbarColor = [240.0f/255.0f, 116.0f/255.0f, 0/255.0f];
const vector clrFailedSlotbarColor = [255.0f/255.0f, 90.0f/255.0f, 100.0f/255.0f];
const vector clrHUDWeaponEmpty = [255.0f/255.0f, 75f/255.0f, 75f/255.0f];
// wait, for the default way the GUI colors work (hud_r, hud_g, hud_b) be 150, 150, 255 then?
// TODO - test that later.
const vector clrPaleBlue = [180.0f/255.0f, 195.0f/255.0f, 255.0f/255.0f];
const vector clrMedRed = [255.0f/255.0f, 56.0f/255.0f, 56.0f/255.0f];
const vector clrGreen = [0f/255.0f, 255.0f/255.0f, 0f/255.0f];
const vector clrRed = [255.0f/255.0f, 0f/255.0f, 0f/255.0f];
const vector clrLaserHUDDot = [255.0f/255.0f, 35.0f/255.0f, 35.0f/255.0f];
// !!!
// g_seatslocal AND pSeatLocal definition moved to seatlocal.h
void HUD_WeaponPickupNotify(int);
// CRITICAL:
// Should these be per pSeat instead? Unsure if that makes sense.
var float TS_keyRefTapped = 0;
var float TS_keyRefUp = 0;
var float TS_keyRefUpASCII = 0;
var float TS_keyRefDown = 0;
var float TS_mouseClickRef = 0;
//TAGGG - NEW.
// The "fov" CVar is set to this at startup. This must be changed to be persistent
// bewteen games.
var float autocvar_fov_default = 80.0f;
//TAGGG - TODO. Is this even used yet?
var int autocvar_cl_autoweaponswitch = TRUE;

146
src/client/draw.qc Normal file
View file

@ -0,0 +1,146 @@
/*
* Copyright (c) 2016-2021 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
// For pain arrows, see Nuclide's "Damage_Draw" method. That's called for in HUD_Draw
// oddly enough, not these event-driven draw methods.
// Also, View_PostDraw is called even before HUD_DRAW, although a draw call early on
// in that method, above the rest should have the same effect anyway.
extern var string g_damage_spr_t;
extern var string g_damage_spr_b;
extern var string g_damage_spr_l;
extern var string g_damage_spr_r;
void drawPainArrows(void);
void drawPainFlash(void);
void ClientGame_DamageDraw(void);
void
ClientGame_PreDraw(void)
{
}
void
ClientGame_PostDraw(void)
{
ClientGame_DamageDraw();
}
// copied from Nuclide
void
drawPainArrows(void)
{
vector center;
vector rel_pos;
float fw, fw_alpha;
float rt, rt_alpha;
if (pSeat->m_flDamageAlpha <= 0.0) {
return;
}
center = video_mins + (video_res / 2);
/* the pos relative to the player + view_dir determines which
* and how bright each indicator is drawn. so first get the relative
* position between us and the attacker, then calculate the strength
* of each direction based on a dotproduct tested against our
* camera direction.
*/
rel_pos = normalize(pSeat->m_vecDamagePos - getproperty(VF_ORIGIN));
makevectors(getproperty(VF_CL_VIEWANGLES));
fw = dotproduct(rel_pos, v_forward);
rt = dotproduct(rel_pos, v_right);
fw_alpha = fabs(fw) * pSeat->m_flDamageAlpha;
if (fw > 0.25f) {
drawpic(center + [-64,-102], g_damage_spr_t,
[128,48], [1,1,1], fw_alpha, DRAWFLAG_ADDITIVE);
} else if (fw < -0.25f) {
drawpic(center + [-64,70], g_damage_spr_b,
[128,48], [1,1,1], fw_alpha, DRAWFLAG_ADDITIVE);
}
rt_alpha = fabs(rt) * pSeat->m_flDamageAlpha;
if (rt > 0.25f) {
drawpic(center + [70,-64], g_damage_spr_r,
[48,128], [1,1,1], rt_alpha, DRAWFLAG_ADDITIVE);
} else if (rt < -0.25f) {
drawpic(center + [-102,-64], g_damage_spr_l,
[48,128], [1,1,1], rt_alpha, DRAWFLAG_ADDITIVE);
}
}
// and now for the screen-wide pain flash.
void
drawPainFlash(void)
{
//drawfill( video_mins, video_res, clrRed, VGUI_WINDOW_FGALPHA );
//drawfill( video_mins, video_res, clrRed, arg_opac - 0.60f, DRAWFLAG_NORMAL );
float drawAlpha;
float filteredAlpha = pSeat->m_flDamageAlpha * 0.49;
if(filteredAlpha > 0.32){
drawAlpha = 0.32;
}else{
drawAlpha = filteredAlpha;
}
drawfill( video_mins, video_res, clrRed, drawAlpha, DRAWFLAG_NORMAL);
}
void
ClientGame_DamageDraw(void){
if (pSeat->m_flDamageAlpha <= 0.0) {
return;
}
drawPainArrows();
drawPainFlash();
// Nuclide's default had no modifier on clframetime ( * 1).
pSeat->m_flDamageAlpha -= clframetime * 1.7;
}

37
src/client/entities.qc Normal file
View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
int
ClientGame_EntityUpdate(float id, float new)
{
switch (id) {
case ENT_POWERUP:
Powerup_Parse();
break;
default:
return (0);
}
return (1);
}
void
ClientGame_EntityRemove(void)
{
if (self.classname == "player")
Player_DestroyWeaponModel((base_player) self);
}

211
src/client/game_event.qc Normal file
View file

@ -0,0 +1,211 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
void
ClientGame_EventParse(float fHeader)
{
player pl = (player)pSeat->m_ePlayer;
vector vecOrigin;
int iSequence;
float fDuration;
float fIdleEndOffset;
int iType;
switch (fHeader) {
case EVENT_TS::PLAYER_DEATH:
// Require a tiny amount of time and a mouse release before a respawn, so that dying
// with the mouse held down isn't enough to trigger a respawn request.
pSeatLocal->m_bNeedPrimaryRelease = TRUE;
pSeatLocal->m_flReleaseTime = time + 0.15;
break;
case EV_OBITUARY:
Obituary_Parse();
break;
case EV_SPARK:
vector vSparkPos, vSparkAngle;
vSparkPos[0] = readcoord();
vSparkPos[1] = readcoord();
vSparkPos[2] = readcoord();
vSparkAngle[0] = readcoord();
vSparkAngle[1] = readcoord();
vSparkAngle[2] = readcoord();
FX_Spark(vSparkPos, vSparkAngle);
break;
case EV_GIBHUMAN:
vector vGibPos;
vGibPos[0] = readcoord();
vGibPos[1] = readcoord();
vGibPos[2] = readcoord();
FX_GibHuman(vGibPos);
break;
case EV_BLOOD:
vector vBloodPos;
vector vBloodColor;
vBloodPos[0] = readcoord();
vBloodPos[1] = readcoord();
vBloodPos[2] = readcoord();
vBloodColor[0] = readbyte() / 255;
vBloodColor[1] = readbyte() / 255;
vBloodColor[2] = readbyte() / 255;
FX_Blood(vBloodPos, vBloodColor);
break;
case EV_EXPLOSION:
vector vExploPos;
vExploPos[0] = readcoord();
vExploPos[1] = readcoord();
vExploPos[2] = readcoord();
FX_Explosion(vExploPos);
break;
case EV_MODELGIB:
vector vecPos;
vecPos[0] = readcoord();
vecPos[1] = readcoord();
vecPos[2] = readcoord();
vector vSize;
vSize[0] = readcoord();
vSize[1] = readcoord();
vSize[2] = readcoord();
float fStyle = readbyte();
int count = readbyte();
FX_BreakModel(count, vecPos, vSize, [0,0,0], fStyle);
break;
case EV_IMPACT:
//int iType;
vector vOrigin, vNormal;
iType = (int)readbyte();
vOrigin[0] = readcoord();
vOrigin[1] = readcoord();
vOrigin[2] = readcoord();
vNormal[0] = readcoord();
vNormal[1] = readcoord();
vNormal[2] = readcoord();
FX_Impact(iType, vOrigin, vNormal);
break;
//TAGGG - NEW
case EVENT_TS::EV_IMPACT_MELEE:
int iType3;
vector vOrigin3;
vector vNormal3;
iType3 = (int)readbyte();
vOrigin3[0] = readcoord();
vOrigin3[1] = readcoord();
vOrigin3[2] = readcoord();
vNormal3[0] = readcoord();
vNormal3[1] = readcoord();
vNormal3[2] = readcoord();
FX_Impact_Melee(iType3, vOrigin3, vNormal3);
break;
case EV_CHAT:
float fSender = readbyte();
float fTeam = readbyte();
string sMessage = readstring();
CSQC_Parse_Print(sprintf("%s: %s", getplayerkeyvalue(fSender, "name"), sMessage), PRINT_CHAT);
break;
case EV_CHAT_TEAM:
float fSender2 = readbyte();
float fTeam2 = readbyte();
string sMessage2 = readstring();
CSQC_Parse_Print(sprintf("[TEAM] %s: %s", getplayerkeyvalue(fSender2, "name"), sMessage2), PRINT_CHAT);
break;
case EV_CHAT_VOX:
Vox_Play(readstring());
break;
case EV_VIEWMODEL:
View_PlayAnimation(readbyte());
break;
case EV_WEAPON_PICKUP:
int w = readbyte();
//TAGGG - NOTE!
// Phase me out, or use me instead. Whichever.
// Redundant with... oh? Nothing here clearly by name.
// Ah well, somewhere there's completly TS independent pickup logic
// that really shouldn't be when some portions of weapons_common.qc and
// an existing weapon pickup object exists, I think.
/*
if (autocvar_cl_autoweaponswitch == 1) {
sendevent("PlayerSwitchWeapon", "i", w);
}
*/
HUD_WeaponPickupNotify(w);
break;
case EVENT_TS::SPAWN:
//time to read the config to send in the weapons one-by-one.
deployConfig();
// so that any choice of weapon, same as before or even nothing, will still
// let client/view.qc do the whole viewmodel routine again
pSeat->m_iLastWeapon = -2;
break;
case EVENT_TS::RESET_VIEW_MODEL:
EV_TS_resetViewModel();
break;
case EVENT_TS::RESET_PLAYER:
int resetInventory = readbyte();
EV_TS_resetPlayer(pl, resetInventory);
break;
//case EVENT_TS::PLAY_INSERT_SHELL_SND:
// EV_TS_PlayInsertShellSound(pl);
// break;
case EVENT_TS::EFFECT_EXPLOSION:
vecOrigin[0] = readcoord();
vecOrigin[1] = readcoord();
vecOrigin[2] = readcoord();
EV_Effect_Explosion( vecOrigin );
break;
case EVENT_TS::EFFECT_SHAKE:
iType = readbyte();
EV_Effect_ScreenShake( iType );
break;
case EVENT_TS::TEST:
//printfline("EVENT_TS::TEST HAPPENED");
//clearscene();
break;
/*
//can this even happen ...?
case EVENT_TS::DROP_WEAPON:
EV_TS_playerDropWeapon(pl);
break;
*/
}
}

5
src/client/hud.h Normal file
View file

@ -0,0 +1,5 @@
// why not? used to be client/defs.h (of this gamemod)
var string g_hud1_spr;

165
src/client/hud.qc Normal file
View file

@ -0,0 +1,165 @@
/*
* Copyright (c) 2016-2021 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
void HUD_DrawWeaponSelect(void);
// Nope, HUD/UI-related precaches in client/precache.qc instead
// ...mostly, keeping this one here?
void
HUD_Init(void)
{
g_hud1_spr = spriteframe("sprites/640hud1.spr", 0, 0.0f);
}
/* weapon/ammo pickup notifications */
void
HUD_DrawNotify(void)
{
// Nope, pretty sure TS had nothing like that.
/*
vector pos;
if (pSeatLocal->m_flPickupAlpha <= 0.0f) {
return;
}
pos = g_hudmins + [g_hudres[0] - 192, g_hudres[1] - 128];
Weapons_HUDPic(pSeatLocal->m_iPickupWeapon, 1, pos, pSeatLocal->m_flPickupAlpha);
pSeatLocal->m_flPickupAlpha -= clframetime;
*/
}
void
HUD_WeaponPickupNotify(int w)
{
//pSeatLocal->m_iPickupWeapon = w;
//pSeatLocal->m_flPickupAlpha = 1.0f;
}
/* main entry */
void
HUD_Draw(void)
{
player pl = (player)pSeat->m_ePlayer;
g_hud_color = autocvar_con_color * (1 / 255);
/* little point in not drawing these, even if you don't have a suit */
Weapons_DrawCrosshair();
HUD_DrawWeaponSelect();
Obituary_Draw();
Textmenu_Draw();
//TAGGG - NEw
//////////////////////////////////////////////////////////////
//View_HandleZoom();
//printfline("SCOPE LEVEL %.2f", pl.flZoomLevel);
if(pl.flZoomLevel < 0.5){ //is this < 40? yes.
HUD_DrawScope();
}else{
// We'll leave details like extra details for the lasersight and the weight bars at a bare minimum
// (should be drawn at all times, oversight in TS 2.1 that they're missing from melee views
// like with knives, katana, corrected in 3.0 of all things)
HUD_DrawCrosshair();
}
//////////////////////////////////////////////////////////////
GameClient_PlayerInputRaw();
//TAGGG - NEw
//////////////////////////////////////////////////////////////
drawTimer();
drawPlayerStats();
//TAGGG - CRITICAL. Nope, Weapons_DrawCrosshair actually calls a weapon's custom HUD drawing method.
// Odd name, but yes, it's not actually focused on just crosshairs. TS uses the more generic
// "HUD_DrawCrosshair" call above.
//drawPlayerCurrentWeaponStats();
//////////////////////////////////////////////////////////////
// TEST! Just for nuclide, doesn't matter what m_iHUDWeaponSelected is exactly,
// just 0 or non-zero has significance in it.
pSeat->m_iHUDWeaponSelected = (pl.weaponSelectHighlightID != -1);
TS_keyRefTapped = 0; //reset.
HUD_DrawNotify();
// Nuclide provided method, draws the HL pain arrows
// Nope! Replaced with a completely new version that does that and more for more control.
// And not even calling from here, leaving that to PostDraw (draw.qc) instead.
// Might stop the pain flash from affecting the color of HUD draw's.
//Damage_Draw();
//ClientGame_DamageDraw();
}
/*
string g_specmodes[] = {
"Free Camera",
"Third Person",
"First Person"
};
*/
// specatator main entry
void
HUD_DrawSpectator(void)
{
Textmenu_Draw();
spectator spec = (spectator)pSeat->m_ePlayer;
drawfont = FONT_20;
/*
vector vecPos;
string strText;
// No need to display these.
strText = sprintf("Tracking: %s", getplayerkeyvalue(spec.spec_ent - 1, "name"));
vecPos[0] = g_hudmins[0] + (g_hudres[0] / 2) - (stringwidth(strText, TRUE, [20,20]) / 2);
vecPos[1] = g_hudmins[1] + g_hudres[1] - 60;
drawstring(vecPos, strText, [20,20], [1,1,1], 1.0f, DRAWFLAG_ADDITIVE);
strText = sprintf("Mode: %s", g_specmodes[spec.spec_mode]);
vecPos[0] = g_hudmins[0] + (g_hudres[0] / 2) - (stringwidth(strText, TRUE, [20,20]) / 2);
vecPos[1] = g_hudmins[1] + g_hudres[1] - 40;
drawstring(vecPos, strText, [20,20], [1,1,1], 1.0f, DRAWFLAG_ADDITIVE);
*/
// TAGGG - could have some message from server-to-client on changing from player to spectator
// to call this only then, but I think doing this every frame for spectator is harmless anyway.
// Changing the FOV isn't necessary, that already comes with the spec/player change, some
// things are nicely defaulted for us in FTE.
setsensitivityscaler(1.0f);
GameClient_SpectatorInputRaw();
drawTimer();
//TAGGG - Moved over! Is it wise for this to go here?
// Links to drawing the MoTD and buymenu when appropriate
//TAGGG - CRITICAL. "self" is a spectator, not a player!!
// Send over the above "spec" instead too!
CSQC_VGUI_Draw( (player)self );
}

266
src/client/hud_crosshair.qc Normal file
View file

@ -0,0 +1,266 @@
/*
* Copyright (c) 2016-2019 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//it's ok for this to go here, right?
CREATE_imageFileRef_t(img_player_stuntmeter1, "sprites/player/divestept.spr", 40, 80)
CREATE_imageFileRef_t(img_player_stuntmeter2, "sprites/player/divestepc.spr", 40, 80)
CREATE_imageFileRef_t(img_player_stuntmeter3, "sprites/player/divestepb.spr", 40, 80)
CREATE_imageFileRef_raw_t(img_quadgraphic, "textures/cross2.tga", 64, 64)
/*
=================
HUD_DrawCrosshair
Draws the cursor every frame, unless spectator
=================
*/
void HUD_DrawCrosshair(void) {
player pl = (player)pSeat->m_ePlayer;
weapondynamic_t dynaRef;
weapondata_gun_t* gunPointer;
weapondata_gun_t gunRef;
vector vCenter = video_mins + [video_res[0]/2, video_res[1]/2];
// only TRUE for guns, melee stuff doesn't get this.
BOOL hasWeaponCrosshair = FALSE;
// only TRUE if the gun has lasersight, of course.
BOOL hasLasersight = FALSE;
int drawLaserDots = 0;
BOOL trackLaserDots = FALSE; //only applicable with lasersight on of course.
if(pl.inventoryEquippedIndex != -1){
//get that weapon.
dynaRef = pl.ary_myWeapons[pl.inventoryEquippedIndex];
if(dynaRef.weaponTypeID == WEAPONDATA_TYPEID_GUN || dynaRef.weaponTypeID == WEAPONDATA_TYPEID_IRONSIGHT){
gunPointer = (weapondata_gun_t*) pl.getEquippedWeaponData();
gunRef = *(gunPointer);
// We probably have a "drawCrosshair" variable in our weapon struct.
// probaly not even necessary really, since we could infer that property fine anyway from the
// weaponTypeID.
// One more check... we can explicitly forbid drawing the crosshair ever for a weapon too.
// Example is the barrett sniper rifle, even hip-fire has no crosshair like other weapons.
if(gunRef.fDrawCrosshair){
hasWeaponCrosshair = TRUE;
}
//we could very well need the lasersight drawn without the crosshair
// (example: barrett sniper rifle)
int iBitsUpgradeSafe_on = dynaRef.iBitsUpgrade_on & (dynaRef.iBitsUpgrade & gunRef.iBitsUpgrade);
//do we have a lasersight though?
if(iBitsUpgradeSafe_on & BITS_WEAPONOPT_LASERSIGHT){
// why yes, we got it
hasLasersight = TRUE;
int reloadSeq = -1;
//what is my reload sequence?
if(dynaRef.weaponTypeID == WEAPONDATA_TYPEID_IRONSIGHT){
//it's from the
weapondata_ironsight_t* ironsightPointer = (weapondata_ironsight_t*)gunPointer;
weapondata_ironsight_t ironsightRef = (*ironsightPointer);
if(!dynaRef.iIronSight){
reloadSeq = ironsightRef.iAnim_Reload_Index;
}else{
reloadSeq = ironsightRef.ironsightdata.iAnim_Reload_Index;
}
}else{
reloadSeq = gunRef.iAnim_Reload_Index;
}
if(!pl.weaponEquippedAkimbo){
drawLaserDots = 1;
}else{
drawLaserDots = 2;
}
// Is this a condition that forbids drawing hte laser dot on the screen?
if(
!pl.isReloading &&
!(pSeat->m_eViewModel.frame == gunRef.iAnim_Deploy_Index && pl.w_attack_next > 0) &&
!pl.lasersightUnlockTime
){
trackLaserDots = FALSE; // force them to the center on the screen.
}else{
// Track their positions on the ingame world. Weird, I know.
trackLaserDots = TRUE;
}
}//END OF lasersight check
}
}//END OF inventoryEquippedIndex check
// Draw some things unconditionally.
// (we only even reached this point if not zoomed through a scope with the overlay of course)
/*
0 - 21 slots: all thirds clear.
22 - 41 slots: top third orange.
42 - 71 slots: top, middle thirds orange.
72 - 81 slots: all thirds orange.
*/
int thirdBelongingTo = 0;
float ratioNumber = getViewPitchRelativeRatio(pl.pitch);
float newDegreeRad = (ratioNumber * (270 - 90) + 90) * (M_PI/180);
//printfline("MY ANGLE? %.2f", (ratioNumber * (270 - 90) + 90) );
//check for the degree, set thirdBelongingTo...
if(ratioNumber <= 0.3333){
thirdBelongingTo = 0;
}else if(ratioNumber <= 0.6667){
thirdBelongingTo = 1;
}else{ // <= 1.0
thirdBelongingTo = 2;
}
// we COULD make this a property (ts/main.c) to be set by the client and read by the server,
// but this might be really inefficient. May as well use the v_angle already available serverside to determine
// what third we're looking at.
// top = 90 degrees for safePlayerViewPitch of -31.
// bottom = 270 degrees for safePlayerViewPitch of 30.
//Max upwards: -31
//mid: 0
//Max downwards: 30
//check for belingong to each of the thirds for which to light up, if applicable. Above that is.
float modder;
vector clrColor;
modder = 0.56f;
if(pl.iTotalSlots <= 21){clrColor = clrPaleBlue;if(thirdBelongingTo==0){modder = 1.0f;}}else{clrColor = clrFailedSlotbarColor;}
DRAW_IMAGE_ADDITIVE(img_player_stuntmeter1, ([vCenter[0] - 40, vCenter[1] - 40]), clrColor, modder)
modder = 0.56f;
if(pl.iTotalSlots <= 41){clrColor = clrPaleBlue;if(thirdBelongingTo==1){modder = 1.0f;}}else{clrColor = clrFailedSlotbarColor;}
DRAW_IMAGE_ADDITIVE(img_player_stuntmeter2, ([vCenter[0] - 40, vCenter[1] - 40]), clrColor, modder)
modder = 0.56f;
if(pl.iTotalSlots <= 71){clrColor = clrPaleBlue;if(thirdBelongingTo==2){modder = 1.0f;}}else{clrColor = clrFailedSlotbarColor;}
DRAW_IMAGE_ADDITIVE(img_player_stuntmeter3, ([vCenter[0] - 40, vCenter[1] - 40]), clrColor, modder)
vector resultArcPoint = vCenter + [cos(newDegreeRad), -sin(newDegreeRad)] * 32;
drawfill( resultArcPoint, [2, 2], clrPaleBlue, 0.7f, DRAWFLAG_NORMAL );
if(hasWeaponCrosshair){
int startCrosshairDist;
int maxCrosshairDelta; //how much the crosshair can move outwards at most. 0 is no change!
int crosshairDist;
if(!hasLasersight){
startCrosshairDist = 22;
maxCrosshairDelta = 15;
}else{
startCrosshairDist = 12;
maxCrosshairDelta = 14;
}
//can expand up to +14 beyond.
float facto = (bound(0, pl.fAccuracyKickback, 0.1) ) / 0.1;
//scale it.
crosshairDist = startCrosshairDist + facto * maxCrosshairDelta;
//printfline("pl.fAccuracyKickback:%.4f", pl.fAccuracyKickback);
clrColor = [0/255.0f, 0/255.0f, 0/255.0f];
DRAW_IMAGE_NORMAL(img_quadgraphic, ([vCenter[0] - 32, vCenter[1] - 32]), clrColor, 1.00f)
// * 0.86 ???
drawfill( [vCenter[0] - crosshairDist, vCenter[1] - 1], [5, 2], clrPaleBlue, 0.70f, DRAWFLAG_NORMAL );
drawfill( [vCenter[0] + (crosshairDist - 5), vCenter[1] - 1], [5, 2], clrPaleBlue, 0.70f, DRAWFLAG_NORMAL );
drawfill( [vCenter[0] - 1, vCenter[1] - crosshairDist], [2, 5], clrPaleBlue, 0.7f, DRAWFLAG_NORMAL );
drawfill( [vCenter[0] - 1, vCenter[1] + (crosshairDist - 5)], [2, 5], clrPaleBlue, 0.70f, DRAWFLAG_NORMAL );
}//END OF ranged weapon check
if(hasLasersight){
if(!trackLaserDots){
// draw it straight at the HUD center.
// TODO - add a very slight, slow random offset from the given point.
// Why? BECAUSE KHORNE MUST BE PLEASED.
// ...I mean, because original TS did it. okay.
vector lasDot;
if(drawLaserDots == 1){
// dot in the center and the range number fresh from the server, probably.
// -1. 5. 2.
lasDot = vCenter + [-1, -1];
drawfill( lasDot, [2, 2], clrLaserHUDDot, 0.83f, DRAWFLAG_NORMAL );
}else if(drawLaserDots == 2){
lasDot = vCenter + [-1 + -4, -1];
drawfill( lasDot, [2, 2], clrLaserHUDDot, 0.83f, DRAWFLAG_NORMAL );
lasDot = vCenter + [-1 + 4, -1];
drawfill( lasDot, [2, 2], clrLaserHUDDot, 0.83f, DRAWFLAG_NORMAL );
}
}else{
if(pl.recentLaserHitPosSet){
if(drawLaserDots == 1){
// dot in the center and the range number fresh from the server, probably.
// -1. 5. 2.
//trust "recentLaserHit" then!
lasDot = project(pl.recentLaserHitPos);
drawfill( lasDot, [2, 2], clrLaserHUDDot, 0.83f, DRAWFLAG_NORMAL );
}else if(drawLaserDots == 2){
lasDot = project(pl.recentLaserHitPos);
drawfill( lasDot, [2, 2], clrLaserHUDDot, 0.83f, DRAWFLAG_NORMAL );
lasDot = project(pl.recentLaserHitPos2);
drawfill( lasDot, [2, 2], clrLaserHUDDot, 0.83f, DRAWFLAG_NORMAL );
}
}
}
if(drawLaserDots != 0){
if(hasWeaponCrosshair){
if(pl.recentLaserHitPosSet){
vector vDistNumberLoc = vCenter + [28, -8];
drawSpriteNumber(ary_LCD_numberSet, vDistNumberLoc.x, vDistNumberLoc.y, pl.recentLaserDistanceDisplay, 3, BITS_DIGITOPT_NONE, clrPaleBlue, 0.91f);
}
}
}//END OF flat drawLaserDots check
}//END OF lasersight check
}//END OF HUD_DrawCrosshair

190
src/client/hud_scope.qc Normal file
View file

@ -0,0 +1,190 @@
/*
* Copyright (c) 2016-2019 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
float fSBOffset;
float fSBScale;
//var vector g_vecLightColor;
CREATE_imageFileRef_raw_t(img_scope_quad, "textures/quarterscope.tga", 256, 256)
CREATE_imageFileRef_raw_t(img_scope_cross_plus, "textures/scope_cross_plus_odd.tga", 19, 19) //was 20, 20.. shoulda been 19,19 tho
//CREATE_imageFileRef_t(img_???, "sprites/player/stand.spr", 64, 48)
/*
=================
HUD_DrawScope_Pic
The scope borders are split up into multiple parts.
We want to fill the screen, so we gotta do some hacking.
=================
*/
void HUD_DrawScope_Pic( vector vPos, vector vSize, string sSprite ) {
drawpic( ( vPos * fSBScale ) + [ fSBOffset, 0 ], sSprite, vSize * fSBScale, '1 1 1', 1.0f );
}
/*
=================
HUD_DrawScope
Tries to draw a scope whenever viewzoom < 1.0f
=================
*/
void HUD_DrawScope( void ) {
player pl = (player)pSeat->m_ePlayer;
if(pl == NULL){
return;
}
// NOTICE - rest of the logic doesn't refer to vCenter except for the lasersight,
// copied from hudcrosshair.
vector vCenter = video_mins + [video_res[0]/2, video_res[1]/2];
// PREDICTION call??
/*
if (self != world) {
//Player_Predict();
//viewClient.vecPlayerOrigin = self.origin;
//viewClient.vecPlayerVelocity = self.velocity;
//viewClient.flMoveFlags = self.pmove_flags;
//viewClient.flGameFlags = self.gflags;
//TAGGG - we get this apparently.
//g_vecLightColor = getlight(viewClient.vecPlayerOrigin) / 255;
//... pSeat->m_vecPredictedOrigin = pl.origin;
g_vecLightColor = getlight(pSeat->m_vecPredictedOrigin) / 255;
}
*/
BOOL hasLasersight = FALSE;
if(pl.inventoryEquippedIndex != -1){
//get that weapon.
weapondynamic_t dynaRef = pl.ary_myWeapons[pl.inventoryEquippedIndex];
weapondata_basic_t* basicPointer = (weapondata_basic_t*) ary_weaponData[dynaRef.weaponID];
weapondata_basic_t basicRef = *(basicPointer);
int myWeaponTypeID = basicRef.typeID;
if(myWeaponTypeID == WEAPONDATA_TYPEID_GUN || myWeaponTypeID == WEAPONDATA_TYPEID_IRONSIGHT){
weapondata_gun_t gunRef = *((weapondata_gun_t*)(basicPointer));
// We probably have a "drawCrosshair" variable in our weapon struct.
// probaly not even necessary really, since we could infer that property fine anyway from the
// typeID.
// One more check... we can explicitly forbid drawing the crosshair ever for a weapon too.
// Example is the barrett sniper rifle, even hip-fire has no crosshair like other weapons.
//if(basicRef.fDrawCrosshair){
//hasWeaponCrosshair = TRUE;
int iBitsUpgradeSafe_on = dynaRef.iBitsUpgrade_on & (dynaRef.iBitsUpgrade & basicRef.iBitsUpgrade);
//do we have a lasersight though?
if(iBitsUpgradeSafe_on & BITS_WEAPONOPT_LASERSIGHT){
// why yes, we got it
hasLasersight = TRUE;
}
//}//END OF fDrawCrosshair check.
}
}//END OF inventoryEquippedIndex check
//How many times does our scope graphic fit half the height of the screen?
float flScopeGraphicScale = (video_res[1]/2) / img_scope_quad.h * 1.6;
vector screenMid = [video_res[0]/2 - flScopeGraphicScale*img_scope_quad.w/2, video_res[1]/2 - flScopeGraphicScale*img_scope_quad.h/2];
const vector vecSca = [flScopeGraphicScale,flScopeGraphicScale];
//Determine whether we need to fill any of the left/right parts of the screen,
//in case the user is using an ultra-wide screen.
float anticipatedSize = flScopeGraphicScale*img_scope_quad.h;
float crosshairX_Offset = 0;
// the "-5" is in case of some overlap with transparent pixels from scaling.
if(anticipatedSize-5 >= video_res[0]/2){
//the graphic will exceed half the screen's width. no need to do anything else.
}else{
float toFillWidth = (video_res[0]/2) - (anticipatedSize);
crosshairX_Offset = toFillWidth;
drawfill( [0,0], [ toFillWidth+5, video_res[1] ], [0,0,0], 1.0f );
drawfill( [ video_res[0]/2 + anticipatedSize-5, 0 ], [video_res[0]/2 + -(anticipatedSize-5-1), video_res[1] ], [0,0,0], 1.0f );
}
if(anticipatedSize-5 >= video_res[1]/2){
//the graphic will exceed half the screen's width. no need to do anything else.
}else{
float toFillHeight = (video_res[1]/2) - (anticipatedSize);
drawfill( [crosshairX_Offset-1,0], [ video_res[0] - (crosshairX_Offset-1)*2, toFillHeight+5 ], [0,0,0], 1.0f );
drawfill( [crosshairX_Offset-1, video_res[1]/2 + anticipatedSize-5 ], [video_res[0] - (crosshairX_Offset-1)*2, video_res[1]/2 + -(anticipatedSize-5-1) ], [0,0,0], 1.0f );
}
float centerCrosshairOpac;
if(video_res[1] < 300){
centerCrosshairOpac = 0; //don't draw
}else{
centerCrosshairOpac = max(0.27f, min(1, (video_res[1] - 300) / (1200 - 300) )* 0.45 ) ;
}
vector vecInset = [0,0];
vector directCenter = [video_res[0]/2, video_res[1]/2];
//DRAW_IMAGE_EXPER(img_scope_cross_plus, directCenter, '0 0', 0, vecSca*0.6, '0 0 0', centerCrosshairOpac );
DRAW_IMAGE_EXPER2(img_scope_cross_plus, directCenter, vecSca, vecInset, '0 0 0', centerCrosshairOpac );
//NOTICE - the drawoption choice of 1 like The Wastes uses only works because the raw color of the
// graphic is expected. But in our case, we actually still want even a black color to be solid.
// The drawoption choice of 1 makes blackness get interpreted as transparency.
// Anything else, just say 0, at least lets us color the white parts black.
// Had to make the black parts still in the image transparent through Gimp, since we changed to a .tga format.
// (the specialists original quarterscope.spr asset just doesn't work)
DRAW_IMAGE_EXPER(img_scope_quad, screenMid, '128 128', 0, vecSca, '0 0 0', 1.0f );
DRAW_IMAGE_EXPER(img_scope_quad, screenMid, '128 128', 90, vecSca, '0 0 0', 1.0f );
DRAW_IMAGE_EXPER(img_scope_quad, screenMid, '128 128', 180, vecSca, '0 0 0', 1.0f );
DRAW_IMAGE_EXPER(img_scope_quad, screenMid, '128 128', 270, vecSca, '0 0 0', 1.0f );
int magAmount = (int)(1.0f / (pl.flZoomLevel ));
//int xOffset = drawSpriteNumber(ary_LCD_numberSet, 24, 24, magAmount, 3, BITS_DIGITOPT_NONE, clrPaleBlue, 0.88f);
//[24 + xOffset + 1, 24 + 4]
Gfx_Text( [24, 27], sprintf("%ix", magAmount), vFontArialScopeMag, clrPaleBlue, 0.95f, DRAWFLAG_ADDITIVE, FONT_ARIAL_SCOPEMAG );
if(hasLasersight){
vector lasDot;
//draw em if ya gottem
lasDot = vCenter + [-1, -1];
drawfill( lasDot, [2, 2], clrRed, 0.70f, DRAWFLAG_NORMAL );
if(pl.recentLaserHitPosSet){
vector vDistNumberLoc = vCenter + [28, -8 - 8];
drawSpriteNumber(ary_LCD_numberSet, vDistNumberLoc.x, vDistNumberLoc.y, pl.recentLaserDistanceDisplay, 3, BITS_DIGITOPT_NONE, clrPaleBlue, 0.91f);
}
}//END OF hasLasersight check
}

View file

@ -0,0 +1,9 @@
//NEW FILE.
//whatever needs to be accessed out of order.
BOOLEAN HUD_DrawWeaponSelect_CheckClick(void);
BOOLEAN HUD_CloseWeaponSelect(BOOL playOffSound);

View file

@ -0,0 +1,884 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
vector g_vecHUDNums[6] =
{
[168 / 256, 72 / 128],
[188 / 256, 72 / 128],
[208 / 256, 72 / 128],
[168 / 256, 92 / 128],
[188 / 256, 92 / 128],
[208 / 256, 92 / 128]
};
var float fHUDWeaponLast;
typedef struct {
string sSprite;
vector vOrigin;
} weaponsymbolinfo_t;
/*
=================
HUD_DrawWeaponSelect_PreviousItem
Checks and returns the next slot with a weapon in it
=================
*/
//TAGGG - REPLACEMENT.
//Now deal with int's (type) instead.
//Also, this takes two parameters now: what index in the player weapon list to start looking for
//the next weapon from, and whether finding the next/previous weapon is restricted
//to some slot in particular, like pressing the "2" key to go through weapons in slot 2
//(on reaching the last weapon, the 1st weapon in slot 2 is selected again, don't move to slot 3).
//Note that this is the same display slot. There is no slot 0.
//A slot of "-1" will be code for, no restriction, feel free to move between slots on trying to go
//to the previous weapon on the first of a slot, or the next weapon on the last of a slot.
//The last optional parameter, neede if freeSlotMovement is false, is what slot the weapon must belong to.
//If we pick a slot different of the currently selected weapon, we start over in the new slot.
int
HUD_DrawWeaponSelect_PreviousItem(int weaponID_toStartFrom, BOOLEAN freeSlotMovement, optional int forcedSlot)
{
player pl = (player)pSeat->m_ePlayer;
// set to TRUE if we start at an invalid range, so that we may include the first valid of any sort.
BOOLEAN canAcceptSame = FALSE;
//printfline("HUD_DrawWeaponSelect_PreviousItem: weaponID_toStartFrom:%i pl.ary_myWeapons_softMax:%i", weaponID_toStartFrom, pl.ary_myWeapons_softMax);
//TAGGG CRITICAL - why wasn't this ">=" and just ">" ???
if(weaponID_toStartFrom >= pl.ary_myWeapons_softMax){
//shouldn't be possible but just in case.
canAcceptSame = TRUE;
return pl.ary_myWeapons_softMax-1;
}
weapondynamic_t dynaRef;
weapondata_basic_t* weaponBasicP;
weapondata_basic_t weaponBasicRef;
int currentSlot = -1;
if(weaponID_toStartFrom != -1){
// Get what slot the currently equipped weapon is on.
// If it doesn't match the forcedSlot (if applicable: 1-5 keys forcing one),
// we start over at a weapon of ID -1 for the next one.
// This happens on switching slots (picking a different 1-5 key) while weapon select
// is open in a different slot, like having some weapon selected ins lot 2 but pressing 5.
// Being in akimbo and requesting a lost other than 5 is also enough to force a reset of weaponID
// to start from.
dynaRef = pl.ary_myWeapons[weaponID_toStartFrom];
if(!freeSlotMovement ){
//That is, if the player was selecting a weapon in akimbo, but the slot is no longer 5..
if(pl.weaponSelectHighlightAkimbo){
if(forcedSlot!=5){
weaponID_toStartFrom = -1; //hack to start at #0 instead
canAcceptSame = FALSE;
//dynaRef = pl.ary_myWeapons[0];
}
}else{
//pl.weaponSelectHighlightAkimbo = FALSE;
weapondata_basic_t* weaponBasicInvGetP = ary_weaponData[dynaRef.weaponID];
weapondata_basic_t weaponBasicInvGetRef = *weaponBasicInvGetP;
currentSlot = weaponBasicInvGetRef.iInventorySlot;
//notice: picking an akimbo weapon choice while a non-akimbo weapon is selected
// will trigger this too, since the forcedSlot will be 5. non-akimbo weapons never have that.
if(currentSlot != forcedSlot){
weaponID_toStartFrom = -1; //hack to start at #0 instead
canAcceptSame = FALSE;
//dynaRef = pl.ary_myWeapons[0];
}
}
pl.weaponSelectHighlightAkimbo = (forcedSlot == 5);
currentSlot = forcedSlot;
}else{
if(!pl.weaponSelectHighlightAkimbo){
//So we have a place to start looking from. Off we go, find the next weapon that belongs to this same slot.
//If we can't find it, we have to decide whether to loop back to the 1st/last weapon of this same slot,
//or move on to the next/previous slot's first/last weapon.
weaponBasicP = ary_weaponData[dynaRef.weaponID];
weaponBasicRef = *weaponBasicP;
currentSlot = weaponBasicRef.iInventorySlot;
}else{
weapondata_basic_t* weaponBasicBaseP = ary_weaponData[dynaRef.weaponID];
weapondata_basic_t weaponBasicBaseRef = *weaponBasicBaseP;
if(weaponBasicBaseRef.iAkimboID > 0 && dynaRef.iBitsUpgrade & BITS_WEAPONOPT_AKIMBO){
weaponBasicP = ary_akimboUpgradeData[weaponBasicBaseRef.iAkimboID];
weaponBasicRef = *weaponBasicP;
//Should always be 5 for akimbo weapons regardless.
//currentSlot = weaponBasicRef.iInventorySlot;
currentSlot = 5;
}else{
//??? clearly can't go through this weapon as akimbo.
pl.weaponSelectHighlightAkimbo = FALSE;
weaponBasicP = ary_weaponData[dynaRef.weaponID];
weaponBasicRef = *weaponBasicP;
currentSlot = weaponBasicRef.iInventorySlot;
}
}
}
}else{
//weaponID_toStartFrom is -1? ok.
canAcceptSame = FALSE;
//dynaRef = pl.ary_myWeapons[0];
pl.weaponSelectHighlightAkimbo = (forcedSlot == 5);
currentSlot = forcedSlot;
}
//we only use the toStartFrom offset for finding the next/previous weapon in the same slot.
//After that we start from the beginning/end of the next/previous slot accordingly for searching.
int weaponID_toStartFromThisSlot = weaponID_toStartFrom;
if(canAcceptSame && weaponID_toStartFromThisSlot != -1){
weaponID_toStartFromThisSlot -= 1;
}
BOOLEAN scheduleTermination = FALSE;
//int lastWeaponToCheck = 0;
for(int slotOffset = 0; slotOffset <= 5; slotOffset++){
int slotToMatch = ((currentSlot + slotOffset - 1) % 5) + 1;
if(slotToMatch < 5){
for(int i = weaponID_toStartFromThisSlot+1; i < pl.ary_myWeapons_softMax; i++){
//where does a weapon match our slot? pick it.
weapondynamic_t dynaOtherRef;
dynaOtherRef = pl.ary_myWeapons[i];
weapondata_basic_t* weaponBasicOtherP = ary_weaponData[dynaOtherRef.weaponID];
weapondata_basic_t weaponBasicOtherRef = *weaponBasicOtherP;
if(weaponBasicOtherRef.iInventorySlot == slotToMatch){
//woohoo
//foundNewWeapon = TRUE;
pl.weaponSelectHighlightAkimbo = FALSE;
//printfline("FOUND NON-AKIMBO WEAPO %i, ITS NAME: %s", i, weaponBasicOtherRef.sDisplayName);
return i;
}
}
}else{
//oh my
//if(pl.weaponSelectHighlightAkimbo){
// //don't reset toStartFrom. Offset is intentional.
//}
for(int i = weaponID_toStartFromThisSlot+1; i < pl.ary_myWeapons_softMax; i++){
//where does a weapon match our slot? pick it.
weapondynamic_t dynaOtherRef;
dynaOtherRef = pl.ary_myWeapons[i];
weapondata_basic_t* weaponBasicOtherP = ary_weaponData[dynaOtherRef.weaponID];
weapondata_basic_t weaponBasicOtherRef = *weaponBasicOtherP;
if(weaponBasicOtherRef.iAkimboID > 0 && dynaOtherRef.iBitsUpgrade & BITS_WEAPONOPT_AKIMBO){
//this weapon is akimbo, yay.
pl.weaponSelectHighlightAkimbo = TRUE;
return i;
}
if(weaponBasicOtherRef.iInventorySlot == slotToMatch){
//To handle stand-alone akimbo weapons found.
if(dynaOtherRef.iBitsUpgrade & BITS_WEAPONOPT_AKIMBO){
pl.weaponSelectHighlightAkimbo = TRUE;
}else{
pl.weaponSelectHighlightAkimbo = FALSE;
}
return i;
}
}
}
//Moving on to check a different slot? Reset weaponID_toStartFromThisSlot then
weaponID_toStartFromThisSlot = -1; //hack to make it start at 0
if(scheduleTermination){
//last shot we got.
break;
}
if(freeSlotMovement == TRUE){
}else{
slotOffset -= 1; //keep the slot exactly the same
scheduleTermination = TRUE;
}
}//END OF LOOP THROUGH SLOTS
//printfline("END. weaponID_toStartFrom:%i", weaponID_toStartFrom);
return weaponID_toStartFrom; //uhhhh. what?? guess there would be no change in this odd case
}//HUD_DrawWeaponSelect_PreviousItem
int
HUD_DrawWeaponSelect_NextItem(int weaponID_toStartFrom, BOOLEAN freeSlotMovement, optional int forcedSlot)
{
player pl = (player)pSeat->m_ePlayer;
// set to TRUE if we start at an invalid range, so that we may include the first valid of any sort.
BOOLEAN canAcceptSame = FALSE;
if(weaponID_toStartFrom >= pl.ary_myWeapons_softMax){
//shouldn't be possible but just in case.
canAcceptSame = TRUE;
return pl.ary_myWeapons_softMax-1;
}
weapondynamic_t dynaRef;
weapondata_basic_t* weaponBasicP;
weapondata_basic_t weaponBasicRef;
int currentSlot = -1;
//printfline("HUD_DrawWeaponSelect_NextItem weaponID_toStartFrom:%i", weaponID_toStartFrom);
if(weaponID_toStartFrom != -1){
dynaRef = pl.ary_myWeapons[weaponID_toStartFrom];
if(!freeSlotMovement ){
//That is, if the player was selecting a weapon in akimbo, but the slot is no longer 5..
if(pl.weaponSelectHighlightAkimbo){
if(forcedSlot!=5){
weaponID_toStartFrom = 1; //hack to start at #max instead
canAcceptSame = FALSE;
//dynaRef = pl.ary_myWeapons[0];
}
}else{
//pl.weaponSelectHighlightAkimbo = FALSE;
weapondata_basic_t* weaponBasicInvGetP = ary_weaponData[dynaRef.weaponID];
weapondata_basic_t weaponBasicInvGetRef = *weaponBasicInvGetP;
currentSlot = weaponBasicInvGetRef.iInventorySlot;
//notice: picking an akimbo weapon choice while a non-akimbo weapon is selected
// will trigger this too, since the forcedSlot will be 5. non-akimbo weapons never have that.
if(currentSlot != forcedSlot){
weaponID_toStartFrom = 1; //hack to start at #max instead
canAcceptSame = FALSE;
//dynaRef = pl.ary_myWeapons[0];
}
}
if(pl.weaponSelectHighlightAkimbo != (forcedSlot==5) ){
weaponID_toStartFrom = 1; //hack to start at #max instead
canAcceptSame = FALSE;
//dynaRef = pl.ary_myWeapons[0];
}else{
}
pl.weaponSelectHighlightAkimbo = (forcedSlot == 5);
currentSlot = forcedSlot;
}else{
if(!pl.weaponSelectHighlightAkimbo){
//So we have a place to start looking from. Off we go, find the next weapon that belongs to this same slot.
//If we can't find it, we have to decide whether to loop back to the 1st/last weapon of this same slot,
//or move on to the next/previous slot's first/last weapon.
weaponBasicP = ary_weaponData[dynaRef.weaponID];
weaponBasicRef = *weaponBasicP;
currentSlot = weaponBasicRef.iInventorySlot;
}else{
weapondata_basic_t* weaponBasicBaseP = ary_weaponData[dynaRef.weaponID];
weapondata_basic_t weaponBasicBaseRef = *weaponBasicBaseP;
if(weaponBasicBaseRef.iAkimboID > 0 && dynaRef.iBitsUpgrade & BITS_WEAPONOPT_AKIMBO){
weaponBasicP = ary_akimboUpgradeData[weaponBasicBaseRef.iAkimboID];
weaponBasicRef = *weaponBasicP;
//Should always be 5 for akimbo weapons regardless.
//currentSlot = weaponBasicRef.iInventorySlot;
currentSlot = 5;
}else{
//??? clearly can't go through this weapon as akimbo.
pl.weaponSelectHighlightAkimbo = FALSE;
weaponBasicP = ary_weaponData[dynaRef.weaponID];
weaponBasicRef = *weaponBasicP;
currentSlot = weaponBasicRef.iInventorySlot;
}
}
}
}else{
// is.. this even possible?
//weaponID_toStartFrom is -1? ok.
// was ... = 1; ?
weaponID_toStartFrom = pl.ary_myWeapons_softMax; //hack to start at #max instead
canAcceptSame = FALSE;
//dynaRef = pl.ary_myWeapons[0];
pl.weaponSelectHighlightAkimbo = (forcedSlot == 5);
currentSlot = forcedSlot;
}
//we only use the toStartFrom offset for finding the next/previous weapon in the same slot.
//After that we start from the beginning/end of the next/previous slot accordingly for searching.
int weaponID_toStartFromThisSlot = weaponID_toStartFrom;
if(canAcceptSame){
weaponID_toStartFromThisSlot += 1;
}
BOOLEAN scheduleTermination = FALSE;
//int lastWeaponToCheck = 0;
//printfline("weaponID_toStartFromThisSlot:%i, canAcceptSame:%d", weaponID_toStartFromThisSlot, canAcceptSame);
for(int slotOffset = 0; slotOffset <= 5; slotOffset++){
//int slotToMatch = ((currentSlot - slotOffset - 1) % 5) + 1;
int slotToMatch = (currentSlot - slotOffset - 1);
if(slotToMatch < 0){
slotToMatch += 5; //easy way to loop around if we go under the minimum slot
}
//printfline("what slot %i", slotToMatch);
slotToMatch = (int)((((float)slotToMatch) % 5)) + 1;
if(slotToMatch < 5){
for(int i = weaponID_toStartFromThisSlot-1; i >= 0; i--){
//where does a weapon match our slot? pick it.
weapondynamic_t dynaOtherRef;
dynaOtherRef = pl.ary_myWeapons[i];
weapondata_basic_t* weaponBasicOtherP = ary_weaponData[dynaOtherRef.weaponID];
weapondata_basic_t weaponBasicOtherRef = *weaponBasicOtherP;
if(weaponBasicOtherRef.iInventorySlot == slotToMatch){
//woohoo
//foundNewWeapon = TRUE;
pl.weaponSelectHighlightAkimbo = FALSE;
//printfline("HUD_DrawWeaponSelect_NextItem NON-AKIMBO WEAPON %i, ITS NAME: %s", i, weaponBasicOtherRef.sDisplayName);
return i;
}
}
}else{
//oh my
//if(pl.weaponSelectHighlightAkimbo){
// //don't reset toStartFrom. Offset is intentional.
//}
for(int i = weaponID_toStartFromThisSlot-1; i >= 0; i--){
// where does a weapon match our slot? pick it.
weapondynamic_t dynaOtherRef;
dynaOtherRef = pl.ary_myWeapons[i];
weapondata_basic_t* weaponBasicOtherP = ary_weaponData[dynaOtherRef.weaponID];
weapondata_basic_t weaponBasicOtherRef = *weaponBasicOtherP;
if(weaponBasicOtherRef.iAkimboID > 0 && dynaOtherRef.iBitsUpgrade & BITS_WEAPONOPT_AKIMBO){
//this weapon is akimbo, yay.
pl.weaponSelectHighlightAkimbo = TRUE;
return i;
}
if(weaponBasicOtherRef.iInventorySlot == slotToMatch){
//To handle stand-alone akimbo weapons found.
if(dynaOtherRef.iBitsUpgrade & BITS_WEAPONOPT_AKIMBO){
pl.weaponSelectHighlightAkimbo = TRUE;
}else{
pl.weaponSelectHighlightAkimbo = FALSE;
}
return i;
}
}
}
//Moving on to check a different slot? Reset weaponID_toStartFromThisSlot then
// hack to make it start at ary_myWeapons_softMax. That is not having "- 1".
weaponID_toStartFromThisSlot = pl.ary_myWeapons_softMax;
if(scheduleTermination){
//last shot we got.
break;
}
if(freeSlotMovement == TRUE){
}else{
slotOffset -= 1; //keep the slot exactly the same
scheduleTermination = TRUE;
}
}//END OF LOOP THROUGH SLOTS
//printfline("END. weaponID_toStartFrom:%i", weaponID_toStartFrom);
// is "- 1" always a good idea here?
return weaponID_toStartFrom - 1; //uhhhh. what?? guess there would be no change in this odd case
}//HUD_DrawWeaponSelect_NextItem
void
HUD_DrawWeaponSelect_Forward(void)
{
player pl = (player)pSeat->m_ePlayer;
if(pl.weaponSelectHighlightID == -1){
//pick the weapon after then
pl.weaponSelectHighlightAkimbo = pl.weaponEquippedAkimbo;
pl.weaponSelectHighlightID = HUD_DrawWeaponSelect_NextItem(pl.inventoryEquippedIndex, TRUE);
//printfline("HUD_DrawWeaponSelect_Forward A? %i", pl.weaponSelectHighlightID);
if(pl.weaponSelectHighlightID != -1){
sound(pl, CHAN_ITEM, "common/wpn_hudon.wav", 0.5, ATTN_NONE);
pSeat->m_flHUDWeaponSelectTime = time + 3;
}else{
pSeat->m_flHUDWeaponSelectTime = 0;
}
}else{
//printfline("HUD_DrawWeaponSelect_Forward B? %i", pl.weaponSelectHighlightID);
pl.weaponSelectHighlightID = HUD_DrawWeaponSelect_NextItem(pl.weaponSelectHighlightID, TRUE);
if(pl.weaponSelectHighlightID != -1){
sound(pl, CHAN_ITEM, "common/wpn_moveselect.wav", 0.5, ATTN_NONE);
pSeat->m_flHUDWeaponSelectTime = time + 3;
}else{
pSeat->m_flHUDWeaponSelectTime = 0;
}
}
}
void
HUD_DrawWeaponSelect_Back(void)
{
player pl = (player)pSeat->m_ePlayer;
if(pl.weaponSelectHighlightID == -1){
//pick the weapon after then
pl.weaponSelectHighlightAkimbo = pl.weaponEquippedAkimbo;
pl.weaponSelectHighlightID = HUD_DrawWeaponSelect_PreviousItem(pl.inventoryEquippedIndex, TRUE);
//printfline("HUD_DrawWeaponSelect_Back C? %i", pl.weaponSelectHighlightID);
if(pl.weaponSelectHighlightID != -1){
sound(pl, CHAN_ITEM, "common/wpn_hudon.wav", 0.5, ATTN_NONE);
pSeat->m_flHUDWeaponSelectTime = time + 3;
}else{
pSeat->m_flHUDWeaponSelectTime = 0;
}
}else{
//printfline("HUD_DrawWeaponSelect_Back D? %i", pl.weaponSelectHighlightID);
pl.weaponSelectHighlightID = HUD_DrawWeaponSelect_PreviousItem(pl.weaponSelectHighlightID, TRUE);
if(pl.weaponSelectHighlightID != -1){
sound(pl, CHAN_ITEM, "common/wpn_moveselect.wav", 0.5, ATTN_NONE);
pSeat->m_flHUDWeaponSelectTime = time + 3;
}else{
pSeat->m_flHUDWeaponSelectTime = 0;
}
}
}
// TODO!!! also for this to be supported instead of HUD_DrawWeaponSelect_CheckClick,
// need to involve "m_iHUDWeaponSelected" elsewhere in this file properly!
// _Trigger is not even called if iHUDWeaponSelected is FALSE !
void
HUD_DrawWeaponSelect_Trigger(void)
{
//TAGGG - TODO - was commented out in old FreeTS, doing so here too.
// But figure out what it needs to do, this likely wasn't pointless
/*
player pl = (player)pSeat->m_ePlayer;
pl.setInventoryEquippedIndex(pSeat->m_iHUDWeaponSelected);
sendevent("PlayerSwitchWeapon", "i", pSeat->m_iHUDWeaponSelected);
sound(pSeat->m_ePlayer, CHAN_ITEM, "common/wpn_select.wav", 0.5f, ATTN_NONE);
pSeat->m_iHUDWeaponSelected = pSeat->m_flHUDWeaponSelectTime = 0;
*/
// Redirect!
// ACTUALLY not yet. This redirect won't work until script
// involving m_iHUDWeaponSelected is properly hooked up!
// NEVERMIND. Attempt to stop holding down primary fire while pushing number keys
// to open weapon-select instantly picking a weapon.
// It works, but can be annoying as this completly blocks the ability to tell
// what is or isn't a fresh key press during that time. Not worth it.
/*
player pl = (player)pSeat->m_ePlayer;
if(pSeat->m_flInputBlockTime > time){
return;
}
*/
HUD_DrawWeaponSelect_CheckClick();
}
//Cheap way to instantly close the weapon select area
BOOLEAN
HUD_CloseWeaponSelect(BOOL playOffSound)
{
player pl = (player)pSeat->m_ePlayer;
if(pSeat->m_flHUDWeaponSelectTime != -1){
if(playOffSound && getplayerkeyvalue(player_localnum, "*spec") == "0" ){
sound(pl, CHAN_ITEM, "common/wpn_hudoff.wav", 0.5, ATTN_NONE);
}
//pSeat->fHUDWeaponSelected = 0; //no harm but no need now
//TAGGG - only makes sense.
pl.weaponSelectHighlightID = -1;
pl.weaponSelectHighlightAkimbo = FALSE;
pSeat->m_flHUDWeaponSelectTime = -1;
return TRUE;
}
return FALSE; //did not.
}
/*
=================
HUD_DrawWeaponSelect
Drawn every frame through HUD.c
=================
*/
void
HUD_DrawWeaponSelect(void)
{
player pl = (player)pSeat->m_ePlayer;
if ( pSeat->m_flHUDWeaponSelectTime < time) {
//if (pSeat->fHUDWeaponSelected) {
// only need to play the sound the first time, but keep blocking
// the below methods.
if(pSeat->m_flHUDWeaponSelectTime != -1){
pSeat->m_flHUDWeaponSelectTime = -1;
//printfline("HUD_DrawWeaponSelect IM closin my weapon select 1");
HUD_CloseWeaponSelect(TRUE);
}
//}
return;
}
//TAGGG - TEST THIS!!! Does the compile order allow this??
drawPlayerInventory_buy(FALSE);
drawPlayerInventory_TopBar(-1, FALSE);
// FreeHL WAY!
/*
player pl = (player)pSeat->m_ePlayer;
if (!pl.inventoryEquippedIndex) {
return;
}
if (pSeat->m_flHUDWeaponSelectTime < time) {
if (pSeat->m_iHUDWeaponSelected) {
sound(pSeat->m_ePlayer, CHAN_ITEM, "common/wpn_hudoff.wav", 0.5, ATTN_NONE);
pSeat->m_iHUDWeaponSelected = 0;
}
return;
}
vector vecPos = g_hudmins + [16,16];
int b;
int wantslot = g_weapons[pSeat->m_iHUDWeaponSelected].slot;
int wantpos = g_weapons[pSeat->m_iHUDWeaponSelected].slot_pos;
for (int i = 0; i < 5; i++) {
int slot_selected = 0;
vecPos[1] = g_hudmins[1] + 16;
HUD_DrawWeaponSelect_Num(vecPos, i);
vecPos[1] += 20;
for (int x = 0; x < 32; x++) {
if (i == wantslot) {
slot_selected = TRUE;
if (x == wantpos) {
// Selected Sprite
Weapons_HUDPic(pSeat->m_iHUDWeaponSelected, 1, vecPos, 1.0f);
drawsubpic(vecPos, [170,45], g_hud3_spr,
[0,180/256], [170/256,45/256], g_hud_color, 1, DRAWFLAG_ADDITIVE);
vecPos[1] += 50;
} else if ((b=HUD_InSlotPos(i, x)) != -1) {
// Unselected Sprite
Weapons_HUDPic(b, 0, vecPos, 1.0f);
vecPos[1] += 50;
}
} else if (HUD_InSlotPos(i, x) != -1) {
HUD_DrawWeaponSelect_Num(vecPos, 5);
vecPos[1] += 25;
}
}
if (slot_selected == TRUE) {
vecPos[0] += 175;
} else {
vecPos[0] += 25;
}
}
*/
}//END OF HUD_DrawWeaponSelect
//if the user clicked (by calling this mehtod we assume they did), we see
//if the user was in weapon select. If so, pick it.
BOOLEAN
HUD_DrawWeaponSelect_CheckClick(void)
{
if(getplayerkeyvalue(player_localnum, "*spec") != "0"){
//some form of spectator? NOT ALLOWED.
//printfline("HEY there closing my weapon select 2");
HUD_CloseWeaponSelect(FALSE);
return FALSE;
}
player pl = (player)pSeat->m_ePlayer;
if(pl.weaponSelectHighlightID != -1){
// clicking picks it then.
pSeat->m_flHUDWeaponSelectTime = -1;
// don't get overridden by the draw sound (CHAN_AUTO, not CHAN_ITEM)
// ...or really, just don't play this at all. The TS weapon is much more silent anyway,
// needs more sound changes. Removals?
//sound(pl, CHAN_AUTO, "common/wpn_select.wav", 0.5f, ATTN_NONE);
TS_playerEquippedWeapon(pl, pl.weaponSelectHighlightID, pl.weaponSelectHighlightAkimbo);
pl.weaponSelectHighlightID = -1;
pl.weaponSelectHighlightAkimbo = FALSE;
return TRUE;
}else{
return FALSE; //didn't change the weapon.
}
}//HUD_DrawWeaponSelect_CheckClick
void
HUD_DrawWeaponSelect_Last(void)
{
//TAGGG - TODO. Support this.
/*
player pl = (player)pSeat->m_ePlayer;
if (pl.g_items & g_weapons[pSeat->m_iOldWeapon].id) {
pl.setInventoryEquippedIndex(pSeat->m_iOldWeapon);
sendevent("PlayerSwitchWeapon", "i", pSeat->m_iOldWeapon);
}
*/
}
/*
void
HUD_DrawWeaponSelect_Num(vector vecPos, float fValue)
{
drawsubpic(vecPos, [20,20], g_hud7_spr, g_vecHUDNums[fValue], [20/256, 20/128], g_hud_color, 1, DRAWFLAG_ADDITIVE);
}
*/
/*
int
HUD_InSlotPos(int slot, int pos)
{
player pl = (player)pSeat->m_ePlayer;
for (int i = 1; i < g_weapons.length; i++) {
if (g_weapons[i].slot == slot && g_weapons[i].slot_pos == pos) {
if (pl.g_items & g_weapons[i].id) {
return i;
} else {
return (-1);
}
}
}
return (-1);
}
*/
void
TS_SelectSlot(int slotPicked)
{
player pl = (player)pSeat->m_ePlayer;
// Keep track of the currently open slot. Navigate through it...
// move to another slot if needed...
//printfline("weapon slot currently highlighted: %i", pl.weaponSelectHighlightID);
if(pl.weaponSelectHighlightID == -1){
//pick the weapon after then
pl.weaponSelectHighlightAkimbo = pl.weaponEquippedAkimbo;
pl.weaponSelectHighlightID = HUD_DrawWeaponSelect_PreviousItem(-1, FALSE, slotPicked);
if(pl.weaponSelectHighlightID != -1){
sound(pl, CHAN_ITEM, "common/wpn_hudon.wav", 0.5, ATTN_NONE);
pSeat->m_flHUDWeaponSelectTime = time + 3;
}else{
pSeat->m_flHUDWeaponSelectTime = 0;
}
}else{
// uhhhh... try going previous of -1 then? Hits the first weapon we got I think.
pl.weaponSelectHighlightID = HUD_DrawWeaponSelect_PreviousItem(pl.weaponSelectHighlightID, FALSE, slotPicked);
if(pl.weaponSelectHighlightID != -1){
sound(pl, CHAN_ITEM, "common/wpn_moveselect.wav", 0.5, ATTN_NONE);
pSeat->m_flHUDWeaponSelectTime = time + 3;
}else{
pSeat->m_flHUDWeaponSelectTime = 0;
}
}
}//TS_SelectSlot
void
HUD_SlotSelect(int slot)
{
player pl = (player)pSeat->m_ePlayer;
//printlinef("HUD_SlotSelect: %i maxweap:%i classname:%s\n", slot, pl.ary_myWeapons_softMax, pl.classname);
if(getplayerkeyvalue(player_localnum, "*spec") != "0"){
// A spectator? Stop.
return;
}
//TAGGG - redirect.
TS_SelectSlot(slot + 1);
/*
player pl = (player)pSeat->m_ePlayer;
int curslot = g_weapons[pSeat->m_iHUDWeaponSelected].slot;
int i;
if (g_textmenu != "") {
Textmenu_Input(slot);
return;
}
// hack to see if we have ANY weapons at all.
if (!pl.inventoryEquippedIndex) {
return;
}
if (pSeat->m_flHUDWeaponSelectTime < time) {
sound(pSeat->m_ePlayer, CHAN_ITEM, "common/wpn_hudon.wav", 0.5, ATTN_NONE);
} else {
sound(pSeat->m_ePlayer, CHAN_ITEM, "common/wpn_moveselect.wav", 0.5, ATTN_NONE);
}
// weren't in that slot? select the first one then
if (curslot != slot) {
for (i = 1; i < g_weapons.length; i++) {
if (g_weapons[i].slot == slot && pl.g_items & g_weapons[i].id) {
pSeat->m_iHUDWeaponSelected = i;
pSeat->m_flHUDWeaponSelectTime = time + 3;
break;
}
}
} else {
int first = -1;
for (i = 1; i < g_weapons.length; i++) {
if (g_weapons[i].slot == slot && pl.g_items & g_weapons[i].id) {
if (i < pSeat->m_iHUDWeaponSelected && first == -1) {
first = i;
} else if (i > pSeat->m_iHUDWeaponSelected) {
first = -1;
pSeat->m_iHUDWeaponSelected = i;
pSeat->m_flHUDWeaponSelectTime = time + 3;
break;
}
}
}
if (first > 0) {
pSeat->m_iHUDWeaponSelected = first;
pSeat->m_flHUDWeaponSelectTime = time + 3;
}
}
*/
}

188
src/client/init.qc Normal file
View file

@ -0,0 +1,188 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
// should this even be defaulted?
var float numclientseats_highest = 0;
/*
=================
ClientGame_Init
Comparable to worldspawn in SSQC in that it's mostly used for precaches
=================
*/
void
ClientGame_Init(float apilevel, string enginename, float engineversion)
{
int s;
printfline("---ClientGame_Init---");
printf("What is numclientseats? %d\n", numclientseats);
// !!!
// Use g_seats.length instead to fill all seats, even those not intended for use, or
// in case of a change of numclientseats (was 1, but someone joins for multiplayer on
// the same machine maybe)? I have no idea how or even if that could happen in the
// same run.
// src/client/view.qc's View_Init works only with the current number of numclientseats
// at least, for reference.
// Although it is called by CSQC_RendererRestarted, which may happen anytime
// numclientseats is adjusted.
// Moved to our ClientGame_RendererRestart for safety, change how this works if my
// understanding of pSeat isn't quite there.
// safety
if(numclientseats > g_seats.length){numclientseats = g_seats.length;}
for(s = numclientseats-1; s >= 0; s--){
pSeat = &g_seats[s];
pSeatLocal = &g_seatslocal[s];
pSeatLocal_init();
ClientInfo_init(&pSeatLocal->m_clientinfo);
}
numclientseats_highest = numclientseats;
//TAGGG - NEW.
SharedGame_Init();
// Nope! Not calling ClientGame_Precache here, use "ClientGame_RendererRestart"
// where all the other precaches are now. It is called at init as well, and
// called later as needed, unlike here.
Obituary_Init();
// over stuff removed
//precache_pic( sprintf( "overviews/%s.bmp", mapname ) );
registercommand("getorigin");
registercommand("getangle");
registercommand("firemode");
registercommand("useitems");
registercommand("usepowerup");
registercommand("+alt1");
registercommand("-alt1");
registercommand("+alt2");
registercommand("-alt2");
registercommand("dev_testorbituary");
registercommand("minimap");
registercommand("overview_test");
registercommand("buy");
registercommand("motd");
//registercommand("chooseteam");
// NO NEED! See CSEv_DropWeapon in nuclide's src/server/weapons.qc, that gets called.
// I think? Might want to verify.
registercommand("drop");
// QUESTION - good luck with however this is supposed to varry between multiple
// split-screen clients (g_seat[#]) across the same game client!
// Can CVars even be per-splitscreen client, yet it feels like some are
// begging to be.
cvar_set("fov", ftos(autocvar_fov_default));
// WARNING! Any font-stuff below may be rendered completley obsolete by
// using the new VGUI approach.
// That also includes mentions of fonts in ts/src/client/vgui.qc
//TAGGG - INCLUSION.
// Also see ts/src/client/vgui.qc where CSQC_VGUI_Draw checks to see if the screen
// size has been changed or this is the first time drawing (some FONTs having ID -1).
// In either case, they're loaded and sized per screen height over there.
FONT_ARIAL = -1; //specify me at draw startup instead, if this is safe.
// This allows this to adjust for screen size.
FONT_ARIAL_TITLE = -1;
// ALSO, beware. arialbd.ttf can be included in projects, (check common gfx/shell
// directories, like within any .pk3dir folders).
// As of this time, the only place should be
// platform/menu_fonts.pk3dir/gfx/shell/arialbd.ttf
// Even so, this reference to the file does not involve the "gfx/shell/" portion,
// so it will only search system libraries for it instead.
// As for how to substitute with the local copy of arialbd.ttf if it is detected
// to be missing from the OS, no idea, because even figuring that out seems trick.
// Any "loadfont" call returns an int that isn't -1 actually, even if it finds
// nothing at all. And consistently, subsequent calls to the exact same file/path
// for "loadfont" will also return the exact same number, as a remembered "type"
// of missing? No idea. (missing uses the FTE default font, which is much more
// sprite/pixel-y looking than most other fonts in lack of a better term).
// BLABLABLA. "arialbd.ttf" without the "gfx/shell/" in front is searching the OS,
// not your own gamemod or platform directories (which ought to have it in gfx/shell).
// Forget it! I'm using our own platform supplied one, "gfx/shell/" it is.
FONT_ARIAL_STD = loadfont( "", "gfx/shell/arialbd.ttf", "14", -1 );
FONT_ARIAL_NUMSLASH = loadfont( "", "gfx/shell/arialbd.ttf", "16", -1 );
FONT_ARIAL_SCOPEMAG = loadfont( "", "gfx/shell/arialbd.ttf", "17", -1 );
// safe default for whatever doesn't specify it?
// Beware that draw calls that don't set the drawfont will still rely
// on the previously set one, although that could be intentional too.
drawfont = FONT_CON;
//////////////////////////////////////////////////////////////////
CSQC_VGUI_Init();
}
void
ClientGame_InitDone(void)
{
}
void
ClientGame_RendererRestart(string rstr)
{
int s;
printfline("---ClientGame_RendererRestart---");
printf("What is numclientseats? %d\n", numclientseats);
// safety
if(numclientseats > g_seats.length){numclientseats = g_seats.length;}
// Did numclientseats change, and it's higher than the previous choice?
// Just init the additional ones then.
if(numclientseats > numclientseats_highest){
for(s = numclientseats-1; s >= numclientseats_highest; s--){
pSeat = &g_seats[s];
pSeatLocal = &g_seatslocal[s];
pSeatLocal_init();
ClientInfo_init(&pSeatLocal->m_clientinfo);
}
numclientseats_highest = numclientseats;
}
//TAGGG - Hook into precache.qc
ClientGame_Precache();
Obituary_Precache();
FX_Blood_Init();
FX_BreakModel_Init();
FX_Explosion_Init();
FX_GibHuman_Init();
FX_Spark_Init();
FX_Impact_Init();
}

4
src/client/input.h Normal file
View file

@ -0,0 +1,4 @@
void GameClient_SpectatorInputRaw(void)
void GameClient_PlayerInputRaw(void);

190
src/client/input.qc Normal file
View file

@ -0,0 +1,190 @@
// NEW FILE.
// WARNING: Do not get this file mixed up with <game>/src/shared/input.qc, that one is
// only called when the player is spawned (collision, seen by others, etc.).
// This is for checking to see if the user performed some action that does not need
// to be checked by the server, such as INPUT_BUTTON0 (primary fire) while not in any
// VGUI choice (blank screen) to send a message to the server to swawn the player.
// This is reached through draw-calls (root of the calls is method CSQC_UpdateView).
// It works.
// ALSO, this is called continually to check for user-provided input, it is not only
// called when user input is detected. Be aware of that.
// Also, this version is for while in spectator. See further down for the "for-player"
// version.
void
GameClient_SpectatorInputRaw(void)
{
// If in spectator with nothing open (no MoTD, no buyside menu),
// go ahead and spawn ingame.
if(pSeatLocal->fVGUI_Display == VGUI_SCREEN::NONE && pSeatLocal->m_flPrevVGUI != VGUI_SCREEN::NONE){
// Current display is NONE, yet the previous wasn't (Recent change to NONE)?
// Set that.
pSeatLocal->m_bNeedPrimaryRelease = TRUE;
pSeatLocal->m_flReleaseTime = time + 0.15f;
}
pSeatLocal->m_flPrevVGUI = pSeatLocal->fVGUI_Display;
// OKAY. So little issue.
// Modern Nuclide does not offer a way to read "input_buttons" in the usual places
// (CSQC_Input_Frame), can they be re-gathered?
// COPIED FROM src/client/predict.qc, for scraping through
// all queued input frames for sending (or not yet verified to have
// been received by the server. I think?).
// Or use the one at clientcommandframe only. Hmm.
// quote from fteextensions.qc:
// The sequence number range used for prediction should normally be
// servercommandframe < sequence <= clientcommandframe.
//printf("WHAT are the client/server comm frames? %d %d\n", clientcommandframe, servercommandframe);
//for (int i = pl.sequence + 1; i <= clientcommandframe; i++) {
/*
bool wasButtonPushedThisFrame = FALSE;
for (int i = servercommandframe+1; i <= clientcommandframe; i++) {
float flSuccess = getinputstate(i);
if (flSuccess == FALSE) {
continue;
}
//if (i==clientcommandframe){
// CSQC_Input_Frame();
//}
if (input_timelength == 0) {
break;
}
if((input_buttons & INPUT_BUTTON0) != 0){
//printfline("IM here man %d\n", (input_buttons & INPUT_BUTTON0) != 0);
// any frame says I got pushed? Treat it as such.
wasButtonPushedThisFrame = TRUE;
}
//input_sequence = i;
}
*/
float flSuccess = getinputstate(clientcommandframe);
if (flSuccess) {
//printf("BUT HOW? %d - %d\n", (pSeatLocal->m_bNeedPrimaryRelease), (( input_buttons & INPUT_BUTTON0)!=0) );
// IDEA: could we just do this?
/*
if(pSeatLocal->m_bNeedPrimaryRelease){
if(!wasButtonPushedThisFrame)){
pSeatLocal->m_bNeedPrimaryRelease = FALSE;
}
}
*/
// INSTEAD OF THIS
///////////////////////////////////////////////////////
if(pSeatLocal->m_bNeedPrimaryRelease){
// yay.
if(!(input_buttons & INPUT_BUTTON0)){
// not pushed? Check it
if(time >= pSeatLocal->m_flReleaseTime){
// okay! Not touched for enough time.
pSeatLocal->m_bNeedPrimaryRelease = FALSE;
}
}else{
// Touched? Oh.
pSeatLocal->m_flReleaseTime = time + 0.15f;
}
}
///////////////////////////////////////////////////////
// primary fire?
if(!pSeatLocal->m_bNeedPrimaryRelease && (input_buttons & INPUT_BUTTON0) ){
if(
pSeatLocal->fVGUI_Display == VGUI_SCREEN::NONE &&
getplayerkeyvalue(player_localnum, "*spec") != "0"
){
// && getstati(STAT_RULE_ALLOW_SPAWN))
// just send the intention we want to spawn, the server will see if a delay is needed.
// And only work if we're not in some other screen AND not spawned. Clicking to spawn while ingame (die) would be irritating.
// Check is no longer needed, only the spectator would have reached this method to begin with.
//if( stof(getplayerkeyvalue(player_localnum, "*team")) == TEAM_SPECTATOR)
//TAGGG - TODO - should some minimum cooldown before respawning be enforced,
// and the countdown shows up if the user clicks too soon since a respawn?
// Print this if the client suspects that will be the case, or let spawn-delay
// be some serverstat that is known here at all times.
//CSQC_Parse_CenterPrint("Spawning soon...\n");
sendevent( "GamePlayerSpawn", "");
VGUI_ChangeScreen(VGUI_SCREEN::NONE);
// probably unnecessary?
EV_TS_resetViewModel();
}
}
}
}//GameClient_SpectatorInputRaw
// While a player.
void
GameClient_PlayerInputRaw(void)
{
player pl = (player)pSeat->m_ePlayer;
// weapon select extra.
if(pl == NULL){
// ???
return;
}
// This was removed? Legacy VGUI
// If we are inside a VGUI, don't let the client do stuff outside
if ((pSeatLocal->fVGUI_Display != VGUI_SCREEN::NONE)) {
pSeat->m_flInputBlockTime = time + 0.2;
}
// we're changing this up a little.
// We'll call a method to let weapon-select related script determine whether
// the player is going through weapon selection when they clicked, not here.
// That is we just call it if a click was detected, it does the rest.
// It will return TRUE if it caused the current weapon to be changed
// (since we don't want to send a fire order below like left-clicking usually does)
// NO NEED FOR THIS CHECK, Nuclide does HUD_DrawWeaponSelect_Trigger on detecting a
// click while something is selected in weapon select.
// WAIT! Keep for now, other things have to be changed internally for _Trigger to
// work right.
/*
// Nuclide calls the Trigger method fine again
if(input_buttons & INPUT_BUTTON0){
if(HUD_DrawWeaponSelect_CheckClick()){
input_buttons = 0;
pSeat->m_flInputBlockTime = time + 0.2;
}
}
*/
//TAGGG - this block is all new. apparently this is right-click.
if(pSeat->m_iInputAttack2){ //input_buttons & INPUT_BUTTON3){
if(HUD_CloseWeaponSelect(TRUE)){
pSeat->m_flInputBlockTime = time + 0.2;
input_impulse = 0;
input_buttons = 0;
pSeat->m_iInputAttack2 = FALSE;
}else{
//pSeat->m_iInputAttack2 = TRUE;
}
}
}//GameClient_PlayerInputRaw

View file

@ -0,0 +1,70 @@
//sample weapon. Fill its sFilePath with whatever weapon to draw and use.
//Better than storing 40+ copies of width/height numbers that never change?
var imageFileRef_t img_weapon = {"", 128, 48};
CREATE_imageFileRef_t(img_item_installed, "sprites/weapons/item_installed.spr", 16, 16)
//item_not_installed.spr ???
CREATE_imageFileRef_t(img_health, "sprites/player/health.spr", 32, 24)
CREATE_imageFileRef_t(img_kevlar, "sprites/player/kevlar.spr", 32, 24)
CREATE_imageFileRef_t(img_player_stand, "sprites/player/stand.spr", 64, 48)
CREATE_imageFileRef_t(img_player_crouch, "sprites/player/crouched.spr", 64, 48)
CREATE_imageFileRef_t(img_player_prone, "sprites/player/prone.spr", 64, 48)
CREATE_imageFileRef_t(img_player_jump, "sprites/player/movement.spr", 48, 48)
CREATE_imageFileRef_t(img_player_kungfu, "sprites/player/kungfu.spr", 48, 48)
//CREATE_imageFileRef_t(img_numbers, "sprites/numbers.spr_0.tga", 112, 16)
CREATE_imageFileRef_t(img_numbers, "sprites/numbers.spr", 112, 16)
//CREATE_imageCropBounds_t(spr_number0, img_numbers, 5, 4, 22, 7);
CREATE_imageCropBounds_t(spr_number0, img_numbers, 1, 0, 10, 16)
CREATE_imageCropBounds_t(spr_number1, img_numbers, 11, 0, 10, 16)
CREATE_imageCropBounds_t(spr_number2, img_numbers, 21, 0, 10, 16)
CREATE_imageCropBounds_t(spr_number3, img_numbers, 32, 0, 10, 16)
CREATE_imageCropBounds_t(spr_number4, img_numbers, 44, 0, 10, 16)
CREATE_imageCropBounds_t(spr_number5, img_numbers, 55, 0, 10, 16)
CREATE_imageCropBounds_t(spr_number6, img_numbers, 67, 0, 10, 16)
CREATE_imageCropBounds_t(spr_number7, img_numbers, 79, 0, 10, 16)
CREATE_imageCropBounds_t(spr_number8, img_numbers, 90, 0, 10, 16)
CREATE_imageCropBounds_t(spr_number9, img_numbers, 102, 0, 10, 16)
imageCropBounds_t ary_LCD_numberSet[] = {
spr_number0,
spr_number1,
spr_number2,
spr_number3,
spr_number4,
spr_number5,
spr_number6,
spr_number7,
spr_number8,
spr_number9
};
void drawWeaponOptionBar(vector arg_vDrawOrigin, string arg_sOptionName, BOOLEAN arg_fBrightLight, float arg_opac);
void drawPlayerInventory_TopBar(int arg_iSlotSelected, BOOL arg_fBuyMode);
void drawPlayerInventory_buy(BOOL arg_fBuyMode);
void drawPlayerInventory_place(int arg_iSlot, int arg_iRow, string arg_sWeaponSpritePath, string arg_sSelectedWeaponDisplayName, BOOL arg_fBuyMode, optional int ammoCount, optional BOOL hasAnyAmmo, optional int bitsUpgradeOpts);
void drawPlayerCurrentWeaponStats(void);
void drawPlayerStats(void);
void drawTimer(void);

View file

@ -0,0 +1,491 @@
// at the bottom-right. Used to show which of the silencer, lasersight, scope, or flashlight was purchased.
void
drawWeaponOptionBar(
vector arg_vDrawOrigin, string arg_sOptionName, BOOLEAN arg_fBrightLight,
float arg_opac
)
{
drawfill( arg_vDrawOrigin, [128, 19], clrPaleBlue, arg_opac - 0.55f );
Gfx_Text( [arg_vDrawOrigin.x + 2, arg_vDrawOrigin.y + 4], arg_sOptionName, vButtonFontSize, clrPaleBlue, 0.90f, DRAWFLAG_ADDITIVE, FONT_ARIAL_STD );
//arg_vDrawOrigin.y -= 20;
//will not work.
vector tempVec = [arg_vDrawOrigin.x + 112, arg_vDrawOrigin.y + 1];
if(arg_fBrightLight){
DRAW_IMAGE_ADDITIVE(img_item_installed, tempVec, clrPaleBlue, arg_opac + 0.15f)
}else{
DRAW_IMAGE_ADDITIVE(img_item_installed, tempVec, clrPaleBlue, arg_opac - 0.50f)
}
}//drawWeaponOptionBar
void
drawPlayerInventory_TopBar(int arg_iSlotSelected, BOOL arg_fBuyMode)
{
vector vWeaponsBar_drawLoc;
vector vWeaponsBar_drawBase;
if(arg_fBuyMode){
// draw the pre-bar
drawfill( [8, 8], [640, 19], clrPaleBlue, 0.96f - 0.60f );
vWeaponsBar_drawBase = [8, 28];
// TODO - fetch these globally
if(!getstati(STAT_RULE_MONEYALLOWED)){
Gfx_Text( [8, 8 + 2], sprintf("Order Value: %i", pSeatLocal->m_clientinfo.weaponconfig_temp.iTotalPrice), vButtonFontSize, clrPaleBlue, 0.93f, DRAWFLAG_ADDITIVE, FONT_ARIAL_STD );
}else{
Gfx_Text( [8, 8 + 2], sprintf("Cash: %i (Order Cost: %i)", getstati(STAT_MONEY), pSeatLocal->m_clientinfo.weaponconfig_temp.iTotalPrice), vButtonFontSize, clrPaleBlue, 0.93f, DRAWFLAG_ADDITIVE, FONT_ARIAL_STD );
}
Gfx_Text( [8 + 128*2, 8 + 2], sprintf("Weight Slots: %i / %i", pSeatLocal->m_clientinfo.weaponconfig_temp.iTotalSlots, getstati(STAT_RULE_MAXWEIGHTSLOTS)), vButtonFontSize, clrPaleBlue, 0.93f, DRAWFLAG_ADDITIVE, FONT_ARIAL_STD );
}else{
vWeaponsBar_drawBase = [8, 8];
}
drawfill( vWeaponsBar_drawBase, [640, 20], clrPaleBlue, 0.96f - 0.60f );
vWeaponsBar_drawLoc.x = vWeaponsBar_drawBase.x + 11 + 128*0;
vWeaponsBar_drawLoc.y = vWeaponsBar_drawBase.y + 0;
DRAW_IMAGE_CROPPED_ADDITIVE(spr_number1, vWeaponsBar_drawLoc, clrPaleBlue, 0.89f)
vWeaponsBar_drawLoc.x = vWeaponsBar_drawBase.x + 11 + 128*1;
vWeaponsBar_drawLoc.y = vWeaponsBar_drawBase.y + 0;
DRAW_IMAGE_CROPPED_ADDITIVE(spr_number2, vWeaponsBar_drawLoc, clrPaleBlue, 0.89f)
vWeaponsBar_drawLoc.x = vWeaponsBar_drawBase.x + 11 + 128*2;
vWeaponsBar_drawLoc.y = vWeaponsBar_drawBase.y + 0;
DRAW_IMAGE_CROPPED_ADDITIVE(spr_number3, vWeaponsBar_drawLoc, clrPaleBlue, 0.89f)
vWeaponsBar_drawLoc.x = vWeaponsBar_drawBase.x + 11 + 128*3;
vWeaponsBar_drawLoc.y = vWeaponsBar_drawBase.y + 0;
DRAW_IMAGE_CROPPED_ADDITIVE(spr_number4, vWeaponsBar_drawLoc, clrPaleBlue, 0.89f)
vWeaponsBar_drawLoc.x = vWeaponsBar_drawBase.x + 11 + 128*4;
vWeaponsBar_drawLoc.y = vWeaponsBar_drawBase.y + 0;
DRAW_IMAGE_CROPPED_ADDITIVE(spr_number5, vWeaponsBar_drawLoc, clrPaleBlue, 0.89f)
}//drawPlayerInventory_TopBar
// While in the buy screen, draw each slot according to the current temp config.
// This includes using the #1 slot for the text
// ...We're going to take advantage of the fact that weaponconfig_weapon_t
void
drawPlayerInventory_buy(BOOL arg_fBuyMode)
{
player pl = (player)pSeat->m_ePlayer;
// Basic version (has things common to both cases). To be set soon for buy menu and
// ingame (spawned - inventory).
// !!! - No longer the case!
// Don't do casting a ary_myWeapons[i] to weapondynamic_t. weaponconfic_weapon_t is
// now a struct so they aren't so easily transferrable.
// Get the info you need from the weapondynamic_t or weaponconfig_weapon_t by the
// same fBuyMode check, don't trust anything else.
weaponconfig_weapon_t* weapon_configRef;
int currentWeaponID;
int currentWeaponCount;
int bitsUpgradeOpts;
int listMax;
BOOL hasAnyAmmo;
// 1 through 5. there is no slot 0.
//
// TODO CRITICAL - check the compile warning here. Why does this happen??
// What row has been drawn yet for each of the player inventory slots? Use to know
// where to draw the next weapon in that slow (below the previously drawn one...
// next row).
int ary_slotRowYet[INVENTORY_SLOT_COUNT];
//ary_slotRowYet = (int) memalloc(INVENTORY_SLOT_COUNT-1);
for(int i = 0; i < INVENTORY_SLOT_COUNT; i++){
ary_slotRowYet[i] = 0;
}
//memfree(ary_slotRowYet);
// Not set for the Buy Menu (data related to clip, which buyOpts are toggled on, etc.
// is not available). The idea is, we can use "weapon_dynamicRef" to get more info
// about the weapon for displaying too in the inventory as needed that the buy menu
// just doesn't have. Example: ingame inv has clip left, but the buy menu does not.
weapondynamic_t weapon_dynamicRef = NULL;
weapondata_gun_t tempGunRef;
if(arg_fBuyMode){
listMax = pSeatLocal->m_clientinfo.weaponconfig_temp.ary_myWeapons_softMax;
}else{
listMax = pl.ary_myWeapons_softMax;
}
for(int i = 0; i < listMax; i++){
if(arg_fBuyMode){
weapon_configRef = (weaponconfig_weapon_t*) &pSeatLocal->m_clientinfo.weaponconfig_temp.ary_myWeapons[i];
currentWeaponID = weapon_configRef->weaponID;
currentWeaponCount = weapon_configRef->iCount;
bitsUpgradeOpts = weapon_configRef->iBitsUpgrade;
}else{
weapon_dynamicRef = (weapondynamic_t) pl.ary_myWeapons[i];
currentWeaponID = weapon_dynamicRef.weaponID;
currentWeaponCount = weapon_dynamicRef.iCount;
bitsUpgradeOpts = weapon_dynamicRef.iBitsUpgrade;
}
weapondata_basic_t* basicPointer = (weapondata_basic_t*) ary_weaponData[currentWeaponID];
weapondata_basic_t basicRef = *(basicPointer);
// If there isn't any type of count (throwables stacked, ammo), this default of
// -1 stays to mean, don't draw any number.
int ammoCount = -1;
if(basicRef.typeID == WEAPONDATA_TYPEID_GUN || basicRef.typeID == WEAPONDATA_TYPEID_IRONSIGHT){
//and the type of ammo is?
weapondata_gun_t gunRef = *((weapondata_gun_t*)basicPointer);
if(arg_fBuyMode){
ammoCount = pSeatLocal->m_clientinfo.weaponconfig_temp.ary_ammoTotal[gunRef.iAmmoDataID];
}else{
ammoCount = pl.ary_ammoTotal[gunRef.iAmmoDataID];
}
}else if(basicRef.typeID == WEAPONDATA_TYPEID_THROWABLE){
weapondata_throwable_t throwableRef = *((weapondata_throwable_t*)basicPointer);
//throwableRef.iMaxCount
ammoCount = currentWeaponCount;
}
// Note that this remains blank if the weapon is not selected. And for ingame (not buy menu) only.
string sWeaponDisplayName = "";
if(!arg_fBuyMode && pl.weaponSelectHighlightID == i){
if(!pl.weaponSelectHighlightAkimbo || basicRef.iAkimboID <= 0){
// Lacking an akimbo link means we ARE the weapon to be selected.
sWeaponDisplayName = basicRef.sDisplayName;
}
}
if(!arg_fBuyMode){
// only a possible consideration ingame, whether to color the weapon icon
// red or not.
if(basicRef.typeID == WEAPONDATA_TYPEID_GUN || basicRef.typeID == WEAPONDATA_TYPEID_IRONSIGHT){
tempGunRef = *((weapondata_gun_t*)basicPointer);
if(bitsUpgradeOpts & BITS_WEAPONOPT_AKIMBO && basicRef.iAkimboID == WEAPON_AKIMBO_UPGRADE_ID::NONE){
// is akimbo, BUT not a separate choice (see below if that is the
// case)? "iClipAkimboLeft" can count too.
hasAnyAmmo = (weapon_dynamicRef.iClipLeft > 0 || weapon_dynamicRef.iClipAkimboLeft > 0 || pl.ary_ammoTotal[tempGunRef.iAmmoDataID] > 0);
}else{
// not akimbo or it's linked as a separate choice? singular clip
// only.
hasAnyAmmo = (weapon_dynamicRef.iClipLeft > 0 || pl.ary_ammoTotal[tempGunRef.iAmmoDataID] > 0);
}
}else{
// not a gun? ok, assume usable.
hasAnyAmmo = TRUE;
}
}else{
// always normal color, can't be 'out of ammo'.
hasAnyAmmo = TRUE;
}
drawPlayerInventory_place(basicRef.iInventorySlot-1, ary_slotRowYet[basicRef.iInventorySlot-1], basicRef.sIconFilePath, sWeaponDisplayName, arg_fBuyMode, ammoCount, hasAnyAmmo, bitsUpgradeOpts );
ary_slotRowYet[basicRef.iInventorySlot-1]++; //next row for the next weapon that gets that same slot.
// NOTICE - "basicRef.iBitsUpgrade" is whether this weapon supports akimbo or
// not (as an upgrade at least).
// TODO - the ingame inventory will just go ahead and make akimbo its own
// separate dynamic weapon data to enter the list. Or will it??
// of player weapons. Much less expensive than re-checking all weapons to see
// what has akimbo or not every time we move up or down a weapon's choice.
// In short, we won't be doing the automatic akimbo-version generation here
// ingame. It will happen naturally because akimbo will be its own weapon.
weapondata_basic_t* akimboPointer = NULL;
if(bitsUpgradeOpts & BITS_WEAPONOPT_AKIMBO && basicRef.iAkimboID > 0){
// draw the akimbo variant.basicRef.iAkimboID
akimboPointer = (weapondata_basic_t*) ary_akimboUpgradeData[basicRef.iAkimboID];
}
// We have akimbo data? show it.
if(akimboPointer != NULL){
weapondata_basic_t akimboRef = *(akimboPointer);
if(!arg_fBuyMode && pl.weaponSelectHighlightID == i && pl.weaponSelectHighlightAkimbo){
sWeaponDisplayName = akimboRef.sDisplayName;
}else{
sWeaponDisplayName = "";
}
if(!arg_fBuyMode){
// only a possible consideration ingame, whether to color the weapon
// icon red or not.
if(
basicRef.typeID == WEAPONDATA_TYPEID_GUN ||
basicRef.typeID == WEAPONDATA_TYPEID_IRONSIGHT
){
// probably had to be a gun to get here, but eh.
// Clearly must be akimbo by link (has a singular form that says
// to render this separately)
tempGunRef = *((weapondata_gun_t*)basicPointer);
hasAnyAmmo = (weapon_dynamicRef.iClipLeft > 0 || weapon_dynamicRef.iClipAkimboLeft > 0 || pl.ary_ammoTotal[tempGunRef.iAmmoDataID] > 0);
}else{
// not a gun? ok, assume usable.
hasAnyAmmo = TRUE;
}
}else{
// always normal color, can't be 'out of ammo'.
hasAnyAmmo = TRUE;
}
// akimbo version always goes to slot #5.
// whatever just leave it up to what the weapondata says. Should refer to
// slot #5 regardless.
drawPlayerInventory_place(akimboRef.iInventorySlot-1, ary_slotRowYet[akimboRef.iInventorySlot-1], akimboRef.sIconFilePath, sWeaponDisplayName, arg_fBuyMode, ammoCount, hasAnyAmmo, bitsUpgradeOpts );
ary_slotRowYet[akimboRef.iInventorySlot-1]++; //ditto for the akimbo slot.
}
}//END OF for all ary_myWeapons (within softMax)
}//END OF drawPlayerInveotry_buy
// "hasAnyAmmo" is, whether this weapon has any ammo in the clip or the ammo pool.
// Original TS behavior is to color red at a clip of 0, even if ammo is in the pool
// but this seems more like an oversight.
// For non-gun/ironsight weapons (knives, etc.) just force this to TRUE to always be
// colored normally.
void
drawPlayerInventory_place
(
int arg_iSlot, int arg_iRow, string arg_sWeaponSpritePath,
string arg_sSelectedWeaponDisplayName, BOOL arg_fBuyMode,
optional int ammoCount, optional BOOL hasAnyAmmo, optional int bitsUpgradeOpts
){
// Where do I start drawing weapons from? Below the top-most bar that's expected
// to be in place.
// In buymode, there are two top-bars. Account for this by drawing an extra 20
// pixels below.
vector vDrawOrigin;
if(!arg_fBuyMode){
vDrawOrigin = [8, 8+20];
}else{
vDrawOrigin = [8, 8+40];
}
vector vDrawPos = [vDrawOrigin.x + arg_iSlot*128, vDrawOrigin.y + arg_iRow*48 ];
float fOpac;
if(arg_sSelectedWeaponDisplayName != ""){
// If the SelectedWeaponDisplayName was provided, this is the selected weapon.
// This text will be drawn to the right of the weapon drawn.
// TODO - this can also just be done separately outside of all inventory item
// weapon icon drawing.
// Then just make arg_sSelectedWeaponDisplayName into arg_fIsSelected.
fOpac = 1.00f;
Gfx_Text( [vDrawPos.x + 128, vDrawPos.y + 4], arg_sSelectedWeaponDisplayName, vButtonFontSize, clrPaleBlue, 0.95f, DRAWFLAG_ADDITIVE, FONT_ARIAL_STD );
}else{
if(arg_fBuyMode){
// always bright?
fOpac = 0.94f;
}else{
fOpac = 0.82f;
}
}
if(arg_fBuyMode && ammoCount != -1){
// draw how much ammo it has (ammo pool only, not the clip... could add it
// first if wanted though but it's not part of the price)
drawSpriteNumber(ary_LCD_numberSet, vDrawPos.x + 3, vDrawPos.y + 3, ammoCount, 3, BITS_DIGITOPT_DEFAULT, clrPaleBlue, 0.88f);
}
if(arg_fBuyMode){
//draw some buy upgrades as three letters each.
string upgradeString = "";
if(bitsUpgradeOpts & BITS_WEAPONOPT_SILENCER){
if(upgradeString == ""){
upgradeString = sprintf("%s", "Sil");
}else{
upgradeString = sprintf("%s,%s", upgradeString, "Sil");
}
}
if(bitsUpgradeOpts & BITS_WEAPONOPT_LASERSIGHT){
if(upgradeString == ""){
upgradeString = sprintf("%s", "Las");
}else{
upgradeString = sprintf("%s,%s", upgradeString, "Las");
}
}
if(bitsUpgradeOpts & BITS_WEAPONOPT_FLASHLIGHT){
if(upgradeString == ""){
upgradeString = sprintf("%s", "Fla");
}else{
upgradeString = sprintf("%s,%s", upgradeString, "Fla");
}
}
if(bitsUpgradeOpts & BITS_WEAPONOPT_SCOPE){
if(upgradeString == ""){
upgradeString = sprintf("%s", "Sco");
}else{
upgradeString = sprintf("%s,%s", upgradeString, "Sco");
}
}
if(upgradeString != ""){
//if there is anything to draw...
Gfx_Text( [vDrawPos.x + 3, vDrawPos.y + 3 + 16], sprintf("(%s)", upgradeString), vButtonFontSize, clrPaleBlue, 0.96f, DRAWFLAG_ADDITIVE, FONT_ARIAL_STD );
}
}//buymodecheck FOR drawing the upgrade options short-hand near the weapon.
img_weapon.sFilePath = sprintf("%s%s", arg_sWeaponSpritePath, "_0.tga");
if(hasAnyAmmo){
DRAW_IMAGE_ADDITIVE(img_weapon, vDrawPos, clrPaleBlue, fOpac)
}else{
// Still selectable unlike in HL (original TS behavior), but just a warning not
// to pick me.
DRAW_IMAGE_ADDITIVE(img_weapon, vDrawPos, clrHUDWeaponEmpty, fOpac)
}
}//drawPlayerInventory_place
void
drawPlayerCurrentWeaponStats(void)
{
player pl = (player)pSeat->m_ePlayer;
if(pl.inventoryEquippedIndex == -1){
return;
}
weapondynamic_t dynaRef = pl.ary_myWeapons[pl.inventoryEquippedIndex];
weapondata_basic_t* basicPointer = (weapondata_basic_t*) pl.getEquippedWeaponData();
weapondata_basic_t basicRef = *(basicPointer);
return basicRef.vOnDrawHUD(pl, dynaRef);
}//drawPlayerCurrentWeaponStats
// See screenshots for what the bottom-left corner of the HUD ingame should look like.
// The text on the very top is the name of the team you're in. It may often coincide
// with player model though. TEAM_PLAY makes it obvious that it's not necessarily that
// always though.
// Draw the player-specific info that always shows up when spawned.
// Team name (if applicable), health, standing/crouch/prone icon, money/slots, etc.
void
drawPlayerStats(void)
{
player pl = (player)pSeat->m_ePlayer;
int drawerX = 8;
int drawerY = video_res[1] - 48;
// TODO intermediate - fetch whether the player is on a team before doing this
// we'll need some sort of "getstati(STAT_RULE_...)" to go through game rules to see
// if we even support teams someplace, but maybe not here.
// We can always let being a member of team "-1" suggest this gamemode has no teams
// too.
// Healthy compromise could be using a getstati to fetch "sRule_TeamNames". If it's
// 0, this gamemode has no teams. Easy peasy.
//if(player.iTeam != TS_Team::TEAM_NONE){
//string myTeamName = ... //can we even fetch from an array using getstati?
string myTeamName = "team name here";
// like pointer vars setup in server/main.c to refer to each index? It might just work.
drawfill( [drawerX, drawerY - 20], [127, 19], clrPaleBlue, 0.96f - 0.60f );
Gfx_Text( [drawerX + 2, drawerY - 20 + 4], myTeamName, vButtonFontSize, clrPaleBlue, 0.96f, DRAWFLAG_ADDITIVE, FONT_ARIAL_STD );
//}
// TODO - heart symbol to the right, color red instead of the health is under 50
// (or at 50 too? unconfirmed yet)
drawfill( [drawerX, drawerY], [127, 39], clrPaleBlue, 0.96f - 0.60f );
vector clrHealth;
if(pl.health >= 50){
clrHealth = clrPaleBlue;
}else{
clrHealth = clrMedRed;
}
drawSpriteNumber(ary_LCD_numberSet, drawerX + 67 - 6, drawerY + 4 - 2, pl.health, 3, BITS_DIGITOPT_DEFAULT, clrHealth, 0.92f);
DRAW_IMAGE_ADDITIVE(img_health, ([drawerX + 96, drawerY - 2]), clrHealth, 0.96f)
// don't render if it's 0.
if(pl.armor > 0){
drawSpriteNumber(ary_LCD_numberSet, drawerX + 67 - 6, drawerY + 24 - 2, pl.armor, 3, BITS_DIGITOPT_DEFAULT, clrPaleBlue, 0.92f);
DRAW_IMAGE_ADDITIVE(img_kevlar, ([drawerX + 96, drawerY + 20 - 1]), clrPaleBlue, 0.96f)
}
// Player status icon (as in standing, crouched, prone, etc.)
// the animated running one looks to be unused. Though I would've seen it by now.
// Regardless...
// TODO. This needs to be synched up to the state of the player, particularly during stunts.
// For now it's static.
// CRITICAL TODO. Support prone / mid-air graphics!
if(self.flags & FL_CROUCHING){
DRAW_IMAGE_ADDITIVE(img_player_crouch, ([drawerX, drawerY - 4]), clrPaleBlue, 0.8f)
}else{
DRAW_IMAGE_ADDITIVE(img_player_stand, ([drawerX, drawerY - 4]), clrPaleBlue, 0.8f)
}
//////////////////////////////////////////////////////////////////////
drawerX = 136;
drawerY = video_res[1] - 48;
int offsetRight = 0;
if(getstati(STAT_RULE_MONEYALLOWED)){
drawfill( [drawerX, drawerY], [127, 19], clrPaleBlue, 0.96f - 0.60f );
offsetRight = drawSpriteNumber(ary_LCD_numberSet, drawerX + 8, drawerY + 2, getstati(STAT_MONEY), 8, BITS_DIGITOPT_NONE, clrPaleBlue, 0.92f);
Gfx_Text( [drawerX + 8 + offsetRight + 6, drawerY + 4], "Credits", vButtonFontSize, clrPaleBlue, 0.96f, DRAWFLAG_ADDITIVE, FONT_ARIAL_STD );
}//money allowed check
drawfill( [drawerX, drawerY + 20], [127, 19], clrPaleBlue, 0.96f - 0.60f );
offsetRight = drawSpriteNumber(ary_LCD_numberSet, drawerX + 8, drawerY + 20 + 2, getstati(STAT_RULE_MAXWEIGHTSLOTS) - pl.iTotalSlots, 8, BITS_DIGITOPT_NONE, clrPaleBlue, 0.92f);
Gfx_Text( [drawerX + 8 + offsetRight + 6, drawerY + 20 + 4], "free slots", vButtonFontSize, clrPaleBlue, 0.96f, DRAWFLAG_ADDITIVE, FONT_ARIAL_STD );
}//drawPlayerStats
void
drawTimer(void)
{
int iMinutes;
int iSeconds;
//video_mins ?
int drawerX = video_res[0] - (60 + 22);
int drawerY = 14 - 2;
if (getstatf(STAT_GAMETIME) == -1) {
return;
}
float gameTime = getstatf(STAT_GAMETIME);
iMinutes = gameTime / 60;
iSeconds = gameTime - 60 * iMinutes;
vector clrDraw;
float fOpac;
float numberFontWidth = ary_LCD_numberSet[0].vSize.x;
if(gameTime < 30){
clrDraw = clrMedRed;
fOpac = 0.98f;
}else{
clrDraw = clrPaleBlue;
fOpac = 0.92f;
}
drawSpriteNumber(ary_LCD_numberSet, drawerX, drawerY, iMinutes, 3, BITS_DIGITOPT_DEFAULT, clrDraw, fOpac);
Gfx_Text( [drawerX + numberFontWidth*3 + 4, drawerY + 1], ":", vFontSizeNumSlash, clrDraw, fOpac, DRAWFLAG_ADDITIVE, FONT_ARIAL_NUMSLASH );
drawSpriteNumber(ary_LCD_numberSet, drawerX + numberFontWidth*4, drawerY, iSeconds, 2, BITS_DIGITOPT_FILLER0, clrDraw, fOpac);
}//drawTimer

48
src/client/obituary.h Normal file
View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#define OBITUARY_LINES 4
#define OBITUARY_TIME 5
/* imagery */
typedef struct {
string name; /* name of the weapon/type, e.g. d_crowbar */
string sprite; /* name of the spritesheet it's from */
float size[2]; /* on-screen size in pixels */
float src_pos[2]; /* normalized position in the sprite sheet */
float src_size[2]; /* normalized size in the sprite sheet */
string src_sprite; /* precaching reasons */
} obituaryimg_t;
obituaryimg_t *g_obtypes;
int g_obtype_count;
/* actual obituary storage */
typedef struct
{
string attacker;
string victim;
int icon;
} obituary_t;
obituary_t g_obituary[OBITUARY_LINES];
int g_obituary_count;
float g_obituary_time;
void Obituary_Init(void);
void Obituary_Precache(void);
void Obituary_Draw(void);
void Obituary_Parse(void);

221
src/client/obituary.qc Normal file
View file

@ -0,0 +1,221 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
void
Obituary_Init(void)
{
int c;
int i;
filestream fh;
string line;
vector tmp;
if (g_obtype_count > 0) {
return;
}
g_obtype_count = 0;
i = 0;
fh = fopen("sprites/hud.txt", FILE_READ);
if (fh < 0) {
return;
}
/* count valid entries */
while ((line = fgets(fh))) {
if (substring(line, 0, 2) == "d_") {
c = tokenize(line);
if (c == 7 && argv(1) == "640") {
g_obtype_count++;
}
}
}
g_obtypes = memalloc(sizeof(obituaryimg_t) * g_obtype_count);
fseek(fh, 0);
/* read them in */
while ((line = fgets(fh))) {
if (substring(line, 0, 2) == "d_") {
c = tokenize(line);
/* we only care about the high-res (640) variants. the 320
* HUD is useless to us. Just use the builtin scaler */
if (c == 7 && argv(1) == "640") {
g_obtypes[i].name = substring(argv(0), 2, -1);
g_obtypes[i].src_sprite = sprintf("sprites/%s.spr", argv(2));
precache_model(g_obtypes[i].src_sprite);
g_obtypes[i].sprite = spriteframe(sprintf("sprites/%s.spr", argv(2)), 0, 0.0f);
g_obtypes[i].size[0] = stof(argv(5));
g_obtypes[i].size[1] = stof(argv(6));
tmp = drawgetimagesize(g_obtypes[i].sprite);
g_obtypes[i].src_pos[0] = stof(argv(3)) / tmp[0];
g_obtypes[i].src_pos[1] = stof(argv(4)) / tmp[1];
g_obtypes[i].src_size[0] = g_obtypes[i].size[0] / tmp[0];
g_obtypes[i].src_size[1] = g_obtypes[i].size[1] / tmp[1];
i++;
}
}
}
fclose(fh);
}
void
Obituary_Precache(void)
{
for (int i = 0; i < g_obtype_count; i++)
precache_model(g_obtypes[i].src_sprite);
}
void
Obituary_KillIcon(int id, float w)
{
if (w > 0)
for (int i = 0; i < g_obtype_count; i++) {
if (g_weapons[w].name == g_obtypes[i].name) {
g_obituary[id].icon = i;
return;
}
}
/* look for skull instead */
for (int i = 0; i < g_obtype_count; i++) {
if (g_obtypes[i].name == "skull") {
g_obituary[id].icon = i;
return;
}
}
}
void
Obituary_Add(string attacker, string victim, float weapon, float flags)
{
int i;
int x, y;
x = OBITUARY_LINES;
/* we're not full yet, so fill up the buffer */
if (g_obituary_count < x) {
y = g_obituary_count;
g_obituary[y].attacker = attacker;
g_obituary[y].victim = victim;
Obituary_KillIcon(y, weapon);
g_obituary_count++;
} else {
for (i = 0; i < (x-1); i++) {
g_obituary[i].attacker = g_obituary[i+1].attacker;
g_obituary[i].victim = g_obituary[i+1].victim;
g_obituary[i].icon = g_obituary[i+1].icon;
}
/* after rearranging, add the newest to the bottom. */
g_obituary[x-1].attacker = attacker;
g_obituary[x-1].victim = victim;
Obituary_KillIcon(x-1, weapon);
}
g_obituary_time = OBITUARY_TIME;
if (g_weapons[weapon].deathmsg) {
string conprint = g_weapons[weapon].deathmsg();
if (conprint != "") {
print(sprintf(conprint, attacker, victim));
print("\n");
}
}
}
void
Obituary_Draw(void)
{
int i;
vector pos;
vector item;
drawfont = FONT_CON;
pos = g_hudmins + [g_hudres[0] - 18, 56];
if (g_obituary_time <= 0 && g_obituary_count > 0) {
for (i = 0; i < (OBITUARY_LINES-1); i++) {
g_obituary[i].attacker = g_obituary[i+1].attacker;
g_obituary[i].victim = g_obituary[i+1].victim;
g_obituary[i].icon = g_obituary[i+1].icon;
}
g_obituary[OBITUARY_LINES-1].attacker = "";
g_obituary_time = OBITUARY_TIME;
g_obituary_count--;
}
if (g_obituary_count <= 0) {
return;
}
item = pos;
for (i = 0; i < OBITUARY_LINES; i++) {
string a, v;
if (!g_obituary[i].attacker) {
break;
}
item[0] = pos[0];
v = g_obituary[i].victim;
drawstring_r(item + [0,2], v, [12,12], [1,1,1], 1.0f, 0);
item[0] -= stringwidth(v, TRUE, [12,12]) + 4;
item[0] -= g_obtypes[g_obituary[i].icon].size[0];
drawsubpic(
item,
[g_obtypes[g_obituary[i].icon].size[0], g_obtypes[g_obituary[i].icon].size[1]],
g_obtypes[g_obituary[i].icon].sprite,
[g_obtypes[g_obituary[i].icon].src_pos[0],g_obtypes[g_obituary[i].icon].src_pos[1]],
[g_obtypes[g_obituary[i].icon].src_size[0],g_obtypes[g_obituary[i].icon].src_size[1]],
g_hud_color,
1.0f,
DRAWFLAG_ADDITIVE
);
a = g_obituary[i].attacker;
drawstring_r(item + [-4,2], a, [12,12], [1,1,1], 1.0f, 0);
item[1] += 18;
}
g_obituary_time = max(0, g_obituary_time - clframetime);
}
void
Obituary_Parse(void)
{
string attacker;
string victim;
float weapon;
float flags;
attacker = readstring();
victim = readstring();
weapon = readbyte();
flags = readbyte();
if (!attacker) {
return;
}
Obituary_Add(attacker, victim, weapon, flags);
}

18
src/client/particles.h Normal file
View file

@ -0,0 +1,18 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
// no, I think not.
//var float BEAM_TRIPMINE;

579
src/client/player.qc Normal file
View file

@ -0,0 +1,579 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
string g_pbones[] =
{
"Bip01",
"Bip01 Footsteps",
"Bip01 Pelvis",
"Bip01 L Leg",
"Bip01 L Leg1",
"Bip01 L Foot",
"Bip01 L Toe0",
"Bip01 L Toe01",
"Bip01 L Toe02",
"Dummy16",
"Bip01 R Leg",
"Bip01 R Leg1",
"Bip01 R Foot",
"Bip01 R Toe0",
"Bip01 R Toe01",
"Bip01 R Toe02",
"Dummy11",
"Bip01 Spine",
"Bip01 Spine1",
"Bip01 Spine2",
"Bip01 Spine3",
"Bip01 Neck",
"Bip01 Head",
"Dummy21",
"Dummy08",
"Bone02",
"Bone03",
"Bone04",
"Dummy05",
"Bone09",
"Bone10",
"Dummy04",
"Bone05",
"Bone06",
"Dummy03",
"Bone07",
"Bone08",
"Dummy09",
"Bone11",
"Bone12",
"Dummy10",
"Bone13",
"Bone14",
"Bone15",
"Bip01 L Arm",
"Bip01 L Arm1",
"Bip01 L Arm2",
"Bip01 L Hand",
"Bip01 L Finger0",
"Bip01 L Finger01",
"Bip01 L Finger02",
"Dummy06",
"Bip01 L Finger1",
"Bip01 L Finger11",
"Bip01 L Finger12",
"Dummy07",
"Bip01 R Arm",
"Bip01 R Arm1",
"Bip01 R Arm2",
"Bip01 R Hand",
"Bip01 R Finger0",
"Bip01 R Finger01",
"Bip01 R Finger02",
"Dummy01",
"Bip01 R Finger1",
"Bip01 R Finger11",
"Bip01 R Finger12",
"Dummy02",
"Box02",
"Bone08",
"Bone15"
};
void
Player_HandleWeaponModel(base_player pp, float thirdperson)
{
player pl = (player)pp;
/* if we don't exist, create us */
if (!pl.p_model) {
pl.p_model = spawn();
}
/* only make it visible when it's a thirdperson drawcall */
pl.p_model.drawmask = (thirdperson) ? MASK_ENGINE:0;
/* let's not waste any time doing bone calculations then */
if (pl.p_model.drawmask == 0)
return;
/* what's the current weapon model supposed to be anyway? */
string wmodel = Weapons_GetPlayermodel(pl.activeweapon);
/* we changed weapons, update skeletonindex */
if (pl.p_model.model != wmodel) {
/* free memory */
if (pl.p_model.skeletonindex)
skel_delete(pl.p_model.skeletonindex);
/* set the new model and mark us updated */
setmodel(pl.p_model, wmodel);
pl.p_model.model = wmodel;
/* set the new skeletonindex */
pl.p_model.skeletonindex = skel_create(pl.p_model.modelindex);
/* hack this thing in here FIXME: this should be done when popping in/out of a pvs */
if (autocvar(cl_himodels, 1, "Use high-quality player models over lower-definition ones"))
setcustomskin(self, "", "geomset 0 2\n");
else
setcustomskin(self, "", "geomset 0 1\n");
}
/* follow player at all times */
setorigin(pl.p_model, pl.origin);
pl.p_model.angles = pl.angles;
skel_build(pl.p_model.skeletonindex, pl.p_model, pl.p_model.modelindex,0, 0, -1);
/* we have to loop through all valid bones of the weapon model and match them
* to the player one */
for (float i = 0; i < g_pbones.length; i++) {
vector bpos;
float pbone = gettagindex(pl, g_pbones[i]);
float wbone = gettagindex(pl.p_model, g_pbones[i]);
/* if the bone doesn't ignore in either skeletal mesh, ignore */
if (wbone <= 0 || pbone <= 0)
continue;
bpos = gettaginfo(pl, pbone);
/* the most expensive bit */
skel_set_bone_world(pl.p_model, wbone, bpos, v_forward, v_right, v_up);
}
}
/* we need to call this when a player entity gets removed */
void
Player_DestroyWeaponModel(entity pp)
{
player pl = (player)pp;
if (pl.p_model)
remove(pl.p_model);
}
//TAGGG - NOTE! In case it ever matters, parameter "base_player pl" renamed to "base_player pp".
// Doubt it ever should, no idea if FTE would complain about a discrepency between prototype and
// implementation parameter names if that ever happened.
void
Player_PreDraw(base_player pp, int thirdperson)
{
//int thirdperson = (autocvar_cl_thirdperson == TRUE || this.entnum != player_localentnum);
//base_player pp = (base_player)this;
BOOL canRenderFlashlight = FALSE;
BOOL canRenderLaserSight = FALSE;
player pl = (player)pp;
//we're going to use the buyopts of our current weapon + the one actually turned on, yah?
// DEBUG: printouts about the other player.
// Start a server with over 1 max players allowed in one window,
// connect to it in another window. Boom, read printouts.
/*
if(entnum != player_localentnum){
// so other player's "pl.weaponEquippedID" are not sent over to our clientside copies of them... I guess?
// At least that's not confusing.
printfline("It is I, the other player! What do I have? %i weapon count: %i", pl.weaponEquippedID, pl.ary_myWeapons_softMax);
}
*/
if(pl.inventoryEquippedIndex != -1){
weapondynamic_t dynaRef = pl.ary_myWeapons[pl.inventoryEquippedIndex];
if(dynaRef.weaponTypeID == WEAPONDATA_TYPEID_GUN || dynaRef.weaponTypeID == WEAPONDATA_TYPEID_IRONSIGHT){
weapondata_basic_t* basePRef = pl.getEquippedWeaponData();
weapondata_basic_t baseRef = *basePRef;
// We must have the flashlight bit on, AND support it on our weapon, AND it be a possibility according to weapon data.
int legalBuyOpts_on = (dynaRef.iBitsUpgrade_on & (dynaRef.iBitsUpgrade & baseRef.iBitsUpgrade));
if(legalBuyOpts_on & BITS_WEAPONOPT_FLASHLIGHT){
canRenderFlashlight = TRUE;
}
if(legalBuyOpts_on & BITS_WEAPONOPT_LASERSIGHT){
canRenderLaserSight = TRUE;
}
}///END OF _GUN or _IRONSIGHT type checks
}//END OF weaponEquippedID check
vector posView;
vector angView;
//for glock it is 40.
//vector gunpos = gettaginfo(pSeat->eViewModel, 33);
float daDrawAlphahz = 1.0;
const vector lasColor = [1.0, 0, 0];
const vector fsize = [2,2];
const vector fsizeDot = [18,18];
const vector fsizeFlashlightMuzzleGlow = [3, 3];
vector flashPos;
vector gunpos;
vector gunpos2 = [0,0,0];
vector gunpos_tempEnd;
vector dirGun = [0,0,0];
vector dirGun2 = [0,0,0];
vector angGun = [0,0,0];
vector angGun2 = [0,0,0];
//TAGGG - IMPORTANT NOTE!!!
// BEWARE "view_angles", it is a client global (what a confusing term), meaning it pertains only to THIS
// client (local player), no matter what player is being rendered by this call.
// wait... shouldn't we do the third-person check for the flash-light check above too?
BOOL canDrawAkimboLaser = FALSE;
pl.recentLaserHitPosSet = TRUE;
// TAGGG - QUESTION: Is it better to use the bool "thirdperson"
// (see check below involving cl_thirdperson)
// OR a raw "pl.entnum != player_localentnum" check?
// The former is a more concrete check for, "Am I the local player
// looking in 1st person, yes or no?".
// The latter will still treat the player being the local player the
// same as firstperson, even if the local player is forcing themselves
// to be viewed in third person.
// I am inclined to think the former is the better choice, but
// valve/src/client/flashlight.qc uses the latter way. Why?
// thirdperson
// True IF (autocvar_cl_thirdperson == TRUE || this.entnum != player_localentnum)
// False IF (autocvar_cl_thirdperson == FALSE && this.entnum == player_localentnum)
if(!thirdperson){
//TAGGG - Old way!
//posView = getproperty(VF_ORIGIN) + [0,0,-8];
//angView = getproperty(VF_CL_VIEWANGLES);
posView = pSeat->m_vecPredictedOrigin + [0,0,-8];
angView = view_angles;
// CHECK: is "getproperty(VF_CL_VIEWANGLES)" always the same as "viewangles"?
if(!pl.weaponEquippedAkimbo){
// first-person and this is the local player?
// We can get more specific info from the visible viewmodel, do so!
gunpos = gettaginfo(pSeat->m_eViewModel, pSeat->m_iVMBones + 0i);
gunpos_tempEnd = gettaginfo(pSeat->m_eViewModel, pSeat->m_iVMBones + 3i);
// should shell casings come from a bit behind the firing point here when needed?
//gunpos += v_right * 0.8; //why is this 'up'??
// not this one maybe... what the hell is this direction at all.
//gunpos += v_forward * -1.8;
dirGun = normalize(gunpos_tempEnd - gunpos);
angGun = vectoangles(dirGun);
}else{
canDrawAkimboLaser = TRUE;
gunpos = gettaginfo(pSeat->m_eViewModel, pSeat->m_iVMBones + 0i);
gunpos_tempEnd = gettaginfo(pSeat->m_eViewModel, pSeat->m_iVMBones + 1i);
dirGun = normalize(gunpos_tempEnd - gunpos);
angGun = vectoangles(dirGun);
gunpos2 = gettaginfo(pSeat->m_eViewModel, pSeat->m_iVMBones + 2i);
gunpos_tempEnd = gettaginfo(pSeat->m_eViewModel, pSeat->m_iVMBones + 3i);
dirGun2 = normalize(gunpos_tempEnd - gunpos2);
angGun2 = vectoangles(dirGun2);
}
}else{
posView = pl.origin + pl.view_ofs;
angView = [pl.pitch, pl.angles[1], pl.angles[2]];
gunpos = posView;
angGun = angView;
}
if(canRenderLaserSight && pl.entnum == player_localentnum){
// The lasersight displays a number of distance to show how long the laser goes.
//makevectors(view_angles);
makevectors(angView);
traceline(posView, posView + v_forward * 2048*4, MOVE_HITMODEL, pl);
pl.recentLaserDistanceDisplay = (int)(vlen(trace_endpos - posView) / 40 );
}
// DEBUG!!!
//canRenderLaserSight = TRUE;
//canRenderFlashlight = TRUE;
if(canRenderFlashlight){
//TAGGG - FLASHLIGHT STUFF HERE..
// oh wait a comment above already said that
// HOWEVER... in TS flashlights have a range limit. Up to so far they have max brightness,
// then it lowers with a bit of range, then it's nothing.
////////////////////////////////////////////////////////////////////////////////////
int flashlightRangeMax = 1070;
float flashlightBrightnessFactor = 1.0;
float rangeDimPriorStart = 170; //rangeMax minus this is where I start dimming.
makevectors(angView);
traceline(posView, posView + (v_forward * flashlightRangeMax), MOVE_NORMAL, pl);
int traceDist = trace_fraction * flashlightRangeMax;
//printfline("%.2f %d",, trace_fraction, trace_inopen);
//TODO - not here but elsewhere, draw the muzzle flashlight effect, some sprite on that gun attachment where am uzzleflash would go should do it.
// ALSO, go ahead and use this line trace to tell the lasersight how far the laser went.
// And just draw the lasersight (and dot if necessary), IF this render is for the local player.
// If not the local player, a slightly larger red dot (actual sprite) goes at the point the
// player is looking, likely not influenced by animations / view-model stuff.
if(trace_fraction == 1.0){
//uh-oh.
flashlightBrightnessFactor = 0;
}if(traceDist >= flashlightRangeMax - rangeDimPriorStart){
//the flashlight gets dimmer the further it is at this point.
// rangeDimPriorStart from the end: max bright still.
// very end: 0% bright.
flashlightBrightnessFactor = (-flashlightRangeMax * (trace_fraction + -1)) / rangeDimPriorStart;
}
if(flashlightBrightnessFactor > 0){
if (serverkeyfloat("*bspversion") == BSPVER_HL) {
dynamiclight_add(trace_endpos + (v_forward * -2), 128 * flashlightBrightnessFactor, [1,1,1]);
} else {
float p = dynamiclight_add(posView, 512 * flashlightBrightnessFactor, [1,1,1], 0, "textures/flashlight");
dynamiclight_set(p, LFIELD_ANGLES, angView);
dynamiclight_set(p, LFIELD_FLAGS, 3);
}
}//END OF brightness check
}//END OF "flashlight is on" criteria
if(canRenderLaserSight || canRenderFlashlight){
// TRY IT SOMEHOW? RF_DEPTHHACK
//pSeat->m_eViewModel.renderflags = RF_DEPTHHACK;
//if (alpha <= 0.0f) {
// return;
//}
makevectors(angGun);
//rotatevectorsbyangle( [-0.42, 0.75, 0] );
//rotatevectorsbyangle( [-0.52, 0.85, 0] );
rotatevectorsbyangle( [-0.45, 0.27, 0] );
flashPos = gunpos + v_up * -0.08 + v_right * 0.06;
vector shortForwardEnd = gunpos;
shortForwardEnd += v_forward * -1;
shortForwardEnd += v_up * (0.22); //why is this 'up'??
shortForwardEnd += v_right * (0.35); //why is this 'up'??
//makevectors(m_vecAngle); really now
//makevectors(input_angles); //maybe this if we need to do this.
// v_up * 2. or.. 1.6, for size [3,3] at least.
// for size [5,5], we need v_up*3, v_right*2. I DONT KNOW.
// for size [2, 2], we want v_up*5, v_right*5. Go figure that one out.
// Keep in mind, the global vectors (v_up, etc.) are set to the orientation of the recently received
// viewmodel attachment (gettaginfo above). Don't 'makevectors' at all to simply rely on that.
// ...unfortunately the orientation we get back is not great either. oh well.
// NEW TEST. Can we even get a straight line from the player's center to the gunpos?
traceline(posView, shortForwardEnd, FALSE, pl);
if(trace_fraction >= 1.0){
//woohoo!
traceline(shortForwardEnd, shortForwardEnd + v_forward * 1024, MOVE_HITMODEL, pl);
// other places care about this.
if (pl.entnum == player_localentnum) {
pl.recentLaserHitPos = trace_endpos;
}
if(canRenderLaserSight){
// In original TS, the 'laster' does not render for the local player if they are
// in thirdperson to see their own playermodel. I... don't really understand that,
// feels like a mistake. The lasers from other players are visible.
// TAGGG - TODO - SUPER LOW PRIORITY
// Lasers only for not in 3rd person and local player.
// Could work for the third-person model or other players if there were a way to determine
// the muzzle-end point for player models. Unsure if that is possible.
// To see it in third-person anyway, just change the "!thirdperson" condition below
// to "TRUE"; always do it. There is no separate place that does only first-person lasers
// to worry about being redundant with.
// Note the laser will try to face the direction the player model is, which may not
// necessarily be where the player is looking, although that same issue would come up
// with firing anyway; you would look like you're firing sideways anyway, this would be
// just as "off".
if (!thirdperson) {
//makevectors(view_angles); //??? it seems we do not need this perhaps...
// IN SHORT, we're riding the prior "makevectors(angGun);".
R_BeginPolygon("sprites/laserbeam.spr_0.tga", 1, 0);
R_PolygonVertex(gunpos + v_right * fsize[0] - v_up * fsize[1], [1,1], lasColor, daDrawAlphahz);
R_PolygonVertex(gunpos - v_right * fsize[0] - v_up * fsize[1], [0,1], lasColor, daDrawAlphahz);
R_PolygonVertex(trace_endpos - v_right * fsize[0] + v_up * fsize[1], [0,0], lasColor, daDrawAlphahz);
R_PolygonVertex(trace_endpos + v_right * fsize[0] + v_up * fsize[1], [1,0], lasColor, daDrawAlphahz);
R_EndPolygon();
}
// Draw the laser sprite effect at where the laser is hitting, but ONLY for every other player
// except this one. That's because, for the local player, we already are drawing the laserdot
// projected onto the screen in the HUD logic.
if (pl.entnum != player_localentnum) {
makevectors(angView);
trace_endpos += trace_plane_normal * fsizeDot[0]/6;
R_BeginPolygon("sprites/laserdot.spr_0.tga", 1, 0);
R_PolygonVertex(trace_endpos + v_right * fsizeDot[0] - v_up * fsizeDot[1], [1,1], lasColor, 0.80f);
R_PolygonVertex(trace_endpos - v_right * fsizeDot[0] - v_up * fsizeDot[1], [0,1], lasColor, 0.80f);
R_PolygonVertex(trace_endpos - v_right * fsizeDot[0] + v_up * fsizeDot[1], [0,0], lasColor, 0.80f);
R_PolygonVertex(trace_endpos + v_right * fsizeDot[0] + v_up * fsizeDot[1], [1,0], lasColor, 0.80f);
R_EndPolygon();
}
}//END OF canRenderLaserSight
// glow effect on top of the gun muzzle while the flashlight is on?
if(canRenderFlashlight){
if(!thirdperson){
makevectors(angView);
//trace_endpos += trace_plane_normal * fsizeDot[0]/6;
R_BeginPolygon("sprites/new/glow02.spr_0.tga", 1, 0);
R_PolygonVertex(flashPos + v_right * fsizeFlashlightMuzzleGlow[0] - v_up * fsizeFlashlightMuzzleGlow[1], [1,1], [1,1,1], 0.45f);
R_PolygonVertex(flashPos - v_right * fsizeFlashlightMuzzleGlow[0] - v_up * fsizeFlashlightMuzzleGlow[1], [0,1], [1,1,1], 0.45f);
R_PolygonVertex(flashPos - v_right * fsizeFlashlightMuzzleGlow[0] + v_up * fsizeFlashlightMuzzleGlow[1], [0,0], [1,1,1], 0.45f);
R_PolygonVertex(flashPos + v_right * fsizeFlashlightMuzzleGlow[0] + v_up * fsizeFlashlightMuzzleGlow[1], [1,0], [1,1,1], 0.45f);
R_EndPolygon();
}
}
}//END OF trace pre-check
// This requires the current weapon to be akimbo, player is in first person,
// and the local player is being rendered.
if(canDrawAkimboLaser){
// test for the 2nd gun's laser too then.
// makevectors(theDir);
makevectors(angGun2);
rotatevectorsbyangle( [-0.45, 0.27, 0] );
//gunpos2 += v_forward * -18;
flashPos = gunpos2 + v_up * -0.08 + v_right * 0.06;
shortForwardEnd = gunpos2;
shortForwardEnd += v_forward * -1;
shortForwardEnd += v_up * (0.22); //why is this 'up'??
shortForwardEnd += -v_right * (0.35); //why is this 'up'??
traceline(posView, shortForwardEnd, FALSE, pl);
if(trace_fraction >= 1.0){
traceline(shortForwardEnd, shortForwardEnd + v_forward * 1024, MOVE_HITMODEL, pl);
// other places care about this.
if (pl.entnum == player_localentnum) {
pl.recentLaserHitPos2 = trace_endpos;
}
if(canRenderLaserSight){
if (!thirdperson) {
// ONLY render the polygon
// makevectors(view_angles); //??? it seems we do not need this perhaps...
// IN SHORT, we're riding the prior "makevectors(angGun2);".
R_BeginPolygon("sprites/laserbeam.spr_0.tga", 1, 0);
R_PolygonVertex(gunpos2 + v_right * fsize[0] - v_up * fsize[1], [1,1], lasColor, daDrawAlphahz);
R_PolygonVertex(gunpos2 - v_right * fsize[0] - v_up * fsize[1], [0,1], lasColor, daDrawAlphahz);
R_PolygonVertex(trace_endpos - v_right * fsize[0] + v_up * fsize[1], [0,0], lasColor, daDrawAlphahz);
R_PolygonVertex(trace_endpos + v_right * fsize[0] + v_up * fsize[1], [1,0], lasColor, daDrawAlphahz);
R_EndPolygon();
}
if (pl.entnum != player_localentnum) {
//makevectors(view_angles);
makevectors(angView);
trace_endpos += trace_plane_normal * fsizeDot[0]/6;
R_BeginPolygon("sprites/laserdot.spr_0.tga", 1, 0);
R_PolygonVertex(trace_endpos + v_right * fsizeDot[0] - v_up * fsizeDot[1], [1,1], lasColor, 0.80f);
R_PolygonVertex(trace_endpos - v_right * fsizeDot[0] - v_up * fsizeDot[1], [0,1], lasColor, 0.80f);
R_PolygonVertex(trace_endpos - v_right * fsizeDot[0] + v_up * fsizeDot[1], [0,0], lasColor, 0.80f);
R_PolygonVertex(trace_endpos + v_right * fsizeDot[0] + v_up * fsizeDot[1], [1,0], lasColor, 0.80f);
R_EndPolygon();
}
}
if(canRenderFlashlight){
if(!thirdperson){
//makevectors(view_angles);
makevectors(angView);
//trace_endpos += trace_plane_normal * fsizeDot[0]/6;
R_BeginPolygon("sprites/new/glow02.spr_0.tga", 1, 0);
R_PolygonVertex(flashPos + v_right * fsizeFlashlightMuzzleGlow[0] - v_up * fsizeFlashlightMuzzleGlow[1], [1,1], [1,1,1], 0.45f);
R_PolygonVertex(flashPos - v_right * fsizeFlashlightMuzzleGlow[0] - v_up * fsizeFlashlightMuzzleGlow[1], [0,1], [1,1,1], 0.45f);
R_PolygonVertex(flashPos - v_right * fsizeFlashlightMuzzleGlow[0] + v_up * fsizeFlashlightMuzzleGlow[1], [0,0], [1,1,1], 0.45f);
R_PolygonVertex(flashPos + v_right * fsizeFlashlightMuzzleGlow[0] + v_up * fsizeFlashlightMuzzleGlow[1], [1,0], [1,1,1], 0.45f);
R_EndPolygon();
}
}
}//END OF trace pre-check
}//END OF akimbo check
/*
if (m_iBeams == 0) {
alpha -= clframetime * 3;
return;
}
*/
}else{
pl.recentLaserHitPosSet = FALSE;
}//END OF canRenderLaserSight || canRenderFlashlight
pl.Physics_SetViewParms();
Animation_PlayerUpdate((player)pl);
Animation_TimerUpdate((player)pl, clframetime);
Player_HandleWeaponModel(pl, thirdperson);
}

8
src/client/precache.h Normal file
View file

@ -0,0 +1,8 @@
var int PART_EXPLOSION;
void ClientGame_Precache(void);

182
src/client/precache.qc Normal file
View file

@ -0,0 +1,182 @@
void ClientGame_Precache(void){
// Yes, this seems to be happening twice at startup since the
// hook from RendererRestart.
// IDEA: test in FreeHL / FreeCS. Do they call RendererRestart twice at startup too?
printfline("***ClientGame_Precache called***");
SharedGame_Precache();
//TAGGG - From FreeHL. Anything that ends up unused by FreeTS can be removed,
// including sound config files that are then unused.
////////////////////////////////////////////////////////////
precache_model("models/shell.mdl");
precache_model("models/shotgunshell.mdl");
precache_model("sprites/muzzleflash1.spr");
precache_model("sprites/muzzleflash2.spr");
precache_model("sprites/muzzleflash3.spr");
Sound_Precache("modelevent_shell.land");
////////////////////////////////////////////////////////////
precache_sound("weapons/draw.wav");
PART_EXPLOSION = particleeffectnum("explosion.explosion_grenade");
precache_model("sprites/mapsprites/ts_gpc1.spr");
precache_model("sprites/mapsprites/ts_gpc2.spr");
precache_model("sprites/player/crouched.spr");
precache_model("sprites/player/divestepb.spr");
precache_model("sprites/player/divestepc.spr");
precache_model("sprites/player/divestept.spr");
precache_model("sprites/player/health.spr");
precache_model("sprites/player/kevlar.spr");
precache_model("sprites/player/kungfu.spr");
precache_model("sprites/player/movement.spr");
precache_model("sprites/player/prone.spr");
precache_model("sprites/player/run.spr");
precache_model("sprites/player/stand.spr");
for(int i = 1; i < WEAPON_ID::LAST_ID; i++){
if(ary_weaponData[i] != NULL){
weapondata_basic_t* basicPointer = (weapondata_basic_t*) ary_weaponData[i];
weapondata_basic_t basicRef = *(basicPointer);
if(basicRef.sIconFilePath != NULL){
precache_model(basicRef.sIconFilePath);
}
}
}
for(int i = 1; i < WEAPON_AKIMBO_UPGRADE_ID::LAST_ID; i++){
if(ary_akimboUpgradeData[i] != NULL){
weapondata_basic_t* basicPointer = (weapondata_basic_t*) ary_akimboUpgradeData[i];
weapondata_basic_t basicRef = *(basicPointer);
if(basicRef.sIconFilePath != NULL){
precache_model(basicRef.sIconFilePath);
}
}
}
precache_model("sprites/weapons/item_installed.spr");
precache_model("sprites/weapons/item_not_installed.spr");
precache_model("sprites/wires/bwire.spr");
precache_model("sprites/wires/bwirecutted.spr");
precache_model("sprites/wires/gwire.spr");
precache_model("sprites/wires/gwirecutted.spr");
precache_model("sprites/wires/rwire.spr");
precache_model("sprites/wires/rwirecutted.spr");
precache_model("sprites/3dm_branch.spr");
precache_model("sprites/3dm_leaves1.spr");
precache_model("sprites/3dm_leaves2.spr");
precache_model("sprites/3dm_leaves3.spr");
precache_model("sprites/3dm_leaves4.spr");
precache_model("sprites/3dm_palm_01.spr");
precache_model("sprites/3dm_palm_02.spr");
precache_model("sprites/3dm_palm_03.spr");
precache_model("sprites/3dm_sfa_tva.spr");
precache_model("sprites/3dm_sfa_tvb.spr");
precache_model("sprites/3dm_sfa_tvc.spr");
precache_model("sprites/3dm_sfa_tvd.spr");
precache_model("sprites/3dm_sfa_tve.spr");
precache_model("sprites/3dm_trunk.spr");
precache_model("sprites/3dm_trunk1.spr");
precache_model("sprites/3dm_trunk2.spr");
precache_model("sprites/black_smoke1.spr");
precache_model("sprites/blood_smoke.spr");
precache_model("sprites/blood_smoke_add.spr");
precache_model("sprites/bloodfrag.spr");
precache_model("sprites/bloodfrag2.spr");
precache_model("sprites/bullet_trail.spr");
precache_model("sprites/bullet_trail2.spr");
precache_model("sprites/bullet_trail3.spr");
precache_model("sprites/bullet_wave.spr");
precache_model("sprites/bullet_wave2.spr");
precache_model("sprites/compass.spr");
precache_model("sprites/conc_decal.spr");
precache_model("sprites/confrag.spr");
precache_model("sprites/cross_e.spr");
precache_model("sprites/cross_t.spr");
//precache_model("sprites/cross2.spr");
precache_pic("textures/cross2.tga");
precache_model("sprites/cursor.spr");
precache_model("sprites/debris_concrete1.spr");
precache_model("sprites/debris_concrete2.spr");
precache_model("sprites/debris_concrete3.spr");
precache_model("sprites/downarrow.spr");
precache_model("sprites/drop.spr");
precache_model("sprites/dust.spr");
precache_model("sprites/flash.spr");
precache_model("sprites/glassfrag.spr");
precache_model("sprites/glow01.spr");
precache_model("sprites/gun_muzzle.spr");
precache_model("sprites/gun_muzzle2.spr");
precache_model("sprites/gun_smoke_add.spr");
precache_model("sprites/knife_decal.spr");
precache_model("sprites/laserbeam.spr");
precache_model("sprites/laserdot.spr");
precache_model("sprites/mazyleaves1.spr");
precache_model("sprites/mazyleaves2.spr");
precache_model("sprites/mazyleaves3.spr");
precache_model("sprites/mazyleaves4.spr");
precache_model("sprites/mazytrunk1.spr");
precache_model("sprites/metal_decal.spr");
precache_model("sprites/muzzle1.spr");
precache_model("sprites/numbers.spr");
precache_model("sprites/ombra.spr");
precache_model("sprites/quarterscope.spr");
precache_model("sprites/redfl1.spr");
precache_model("sprites/scintille.spr");
precache_model("sprites/scintille_metal.spr");
precache_model("sprites/scintille2.spr");
precache_model("sprites/shotgun_pellets.spr");
precache_model("sprites/slowmotion.spr");
precache_model("sprites/ts_muzzleflash1.spr");
precache_model("sprites/ts_muzzleflash1h.spr");
precache_model("sprites/ts_muzzleflash1s.spr");
precache_model("sprites/ts_muzzleflash2h.spr");
precache_model("sprites/ts_muzzleflash6.spr");
precache_model("sprites/ts_muzzleflash6b.spr");
precache_model("sprites/uparrow.spr");
precache_model("sprites/wood_decal.spr");
precache_model("sprites/wood_smoke_add.spr");
precache_model("sprites/woodfrag.spr");
precache_model("sprites/xsmoke1.spr");
// for tga's only. drop the extension... I think.
precache_pic("textures/quarterscope.tga");
//precache_pic("textures/scope_cross_plus.tga");
precache_pic("textures/scope_cross_plus_odd.tga");
//precache_pic("textures/quarterscope_small.tga");
//precache_pic("textures/cal_9x18mm.tga");
//...do we know what of these we even need?
precache_model("sprites/fexplo.spr");
precache_sound("common/wpn_hudon.wav");
precache_sound("common/wpn_hudoff.wav");
precache_sound("common/wpn_moveselect.wav");
precache_sound("common/wpn_select.wav");
}

104
src/client/progs.src Normal file
View file

@ -0,0 +1,104 @@
#pragma target fte
#pragma progs_dat "../../csprogs.dat"
#define CSQC
#define CLIENT
#define TS
//TAGGG - do we want this? FreeHL had it I think, FreeCS definitely does
#define CLASSIC_VGUI
#define GS_RENDERFX
// NEW. When present, the origin is exactly at the center of the player, no further offsets.
// Keeps the view identical on the player dying and becoming spectator in place.
//#define DEBUG_FORCE_NO_VIEW_OFFSET
#includelist
../../../src/shared/fteextensions.qc
../../../src/shared/defs.h
../../../src/client/defs.h
//TAGGG - NEW
../shared/util.h
util.h
../shared/defs.h
defs.h
../shared/ammo.h
../shared/weapons.h
../shared/player.h
../shared/powerup.h
clientinfo.h
seatlocal.h
input.h
//TAGGG - NEW
precache.h
vgui.h
ui_eventgrabber.h
vgui_buysidemenu.h
hud_weaponselect.h
inventory_logic_draw.h
view.h
hud.h
// Yes, really, server/entity... We have a clientside rendering component to this.
../server/entity/ts_powerup.h
../../../src/vgui/include.src
../../../src/gs-entbase/client.src
../../../src/gs-entbase/shared.src
../shared/include.src
../server/entity/ts_powerup.qc
../shared/player.qc
../shared/inventory_logic.qc
//TAGGG - NEW
input.qc
vguiobjects.qc
vgui_motd.qc
vgui_buysidemenu.qc
//vgui_spectator.c ????
vgui.qc
hud_crosshair.qc
hud_scope.qc
draw.qc
init.qc
seatlocal.qc
player.qc
clientinfo.qc
entities.qc
cmds.qc
game_event.qc
view.qc
obituary.qc
hud.qc
hud_weaponselect.qc
inventory_logic_draw.qc
scoreboard.qc
../../../base/src/client/modelevent.qc
//TAGGG - NEW
util.qc
precache.qc
//../_base/client/voice.c
//../_base/client/sound.c
//../_base/client/music.c
//../_base/client/prints.c
//../_base/client/util.c
../../../src/client/include.src
../../../src/shared/include.src
ui_eventgrabber.qc
#endlist

190
src/client/scoreboard.qc Normal file
View file

@ -0,0 +1,190 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#define SCORE_HEADER_C [255/255,156/255,0]
#define SCORE_LINE_C [255/255,200/255,0]
var int autocvar_cl_centerscores = FALSE;
var int g_scores_teamplay = 0;
void
Scores_Init(void)
{
g_scores_teamplay = (int)serverkeyfloat("teamplay");
}
void
Scores_DrawTeam(player pl, vector pos)
{
drawfill(pos, [290, 1], SCORE_LINE_C, 1.0f, DRAWFLAG_ADDITIVE);
drawfont = FONT_20;
drawstring(pos + [0,-18], "Teams", [20,20], SCORE_HEADER_C, 1.0f, DRAWFLAG_ADDITIVE);
drawstring(pos + [124,-18], "kills / deaths", [20,20], SCORE_HEADER_C, 1.0f, DRAWFLAG_ADDITIVE);
drawstring(pos + [240,-18], "latency", [20,20], SCORE_HEADER_C, 1.0f, DRAWFLAG_ADDITIVE);
pos[1] += 12;
for (int t = 1; t <= serverkeyfloat("teams"); t++) {
float l;
string temp;
drawstring(pos, serverkey(sprintf("team_%i", t)), [20,20], SCORE_HEADER_C, 1.0f, DRAWFLAG_ADDITIVE);
temp = serverkey(sprintf("teamscore_%i", t));
l = stringwidth(temp, FALSE, [20,20]);
drawstring(pos + [150-l, 0], temp, [20,20], SCORE_HEADER_C, 1.0f, DRAWFLAG_ADDITIVE);
drawstring(pos + [158, 0], "wins", [20,20], SCORE_HEADER_C, 1.0f, DRAWFLAG_ADDITIVE);
pos[1] += 16;
for (int i = -1; i > -32; i--) {
if (getplayerkeyfloat(i, "*team") != t) {
continue;
}
temp = getplayerkeyvalue(i, "name");
/* Out of players */
if (!temp) {
break;
} else if (temp == getplayerkeyvalue(pl.entnum-1, "name")) {
drawfill(pos, [290, 13], [0,0,1], 0.5f, DRAWFLAG_ADDITIVE);
}
drawstring(pos + [24,0], getplayerkeyvalue(i, "name"), [20,20], [1,1,1], 1.0f, DRAWFLAG_ADDITIVE);
drawstring(pos + [154,0], "/", [20,20], [1,1,1], 1.0f, DRAWFLAG_ADDITIVE);
/* Get the kills and align them left to right */
temp = getplayerkeyvalue(i, "frags");
l = stringwidth(temp, FALSE, [20,20]);
drawstring(pos + [150 - l,0], temp, [20,20], [1,1,1], 1.0f, DRAWFLAG_ADDITIVE);
/* Deaths are right to left aligned */
temp = getplayerkeyvalue(i, "*deaths");
drawstring(pos + [165,0], temp, [20,20], [1,1,1], 1.0f, DRAWFLAG_ADDITIVE);
/* Get the latency and align it left to right */
temp = getplayerkeyvalue(i, "ping");
l = stringwidth(temp, FALSE, [20,20]);
//TAGGG - IMPORTANT!
// I don't know if TS does this too, but its scoreboard screen looks a lot like Half-Life's
// so keeping this for now?
if (getplayerkeyfloat(i, "*dead") == 1) {
drawsubpic(
pos - [8,0],
[32,16],
g_hud1_spr,
[224/256, 240/256],
[32/256, 16/256],
[1,0,0],
1.0f,
DRAWFLAG_ADDITIVE
);
}
drawstring(pos + [290 - l,0], temp, [20,20], [1,1,1], 1.0f, DRAWFLAG_ADDITIVE);
pos[1] += 20;
}
pos[1] += 12;
}
drawfont = FONT_CON;
}
void
Scores_DrawNormal(player pl, vector pos)
{
drawfill(pos, [290, 1], SCORE_LINE_C, 1.0f, DRAWFLAG_ADDITIVE);
drawfont = FONT_20;
drawstring(pos + [0,-18], "Player", [20,20], SCORE_HEADER_C, 1.0f, DRAWFLAG_ADDITIVE);
drawstring(pos + [124,-18], "kills / deaths", [20,20], SCORE_HEADER_C, 1.0f, DRAWFLAG_ADDITIVE);
drawstring(pos + [240,-18], "latency", [20,20], SCORE_HEADER_C, 1.0f, DRAWFLAG_ADDITIVE);
pos[1] += 12;
for (int i = -1; i > -32; i--) {
float l;
string ping;
string kills;
string deaths;
string name;
if (getplayerkeyfloat(i, "*spec") != 0) {
continue;
}
name = getplayerkeyvalue(i, "name");
/* Out of players */
if (!name) {
break;
} else if (name == getplayerkeyvalue(pl.entnum-1, "name")) {
drawfill(pos, [290, 13], [0,0,1], 0.5f, DRAWFLAG_ADDITIVE);
}
drawstring(pos, getplayerkeyvalue(i, "name"), [20,20], [1,1,1], 1.0f, DRAWFLAG_ADDITIVE);
drawstring(pos + [154,0], "/", [20,20], [1,1,1], 1.0f, DRAWFLAG_ADDITIVE);
/* Get the kills and align them left to right */
kills = getplayerkeyvalue(i, "frags");
l = stringwidth(kills, FALSE, [20,20]);
drawstring(pos + [150 - l,0], kills, [20,20], [1,1,1], 1.0f, DRAWFLAG_ADDITIVE);
/* Deaths are right to left aligned */
deaths = getplayerkeyvalue(i, "*deaths");
drawstring(pos + [165,0], deaths, [20,20], [1,1,1], 1.0f, DRAWFLAG_ADDITIVE);
/* Get the latency and align it left to right */
ping = getplayerkeyvalue(i, "ping");
l = stringwidth(ping, FALSE, [20,20]);
drawstring(pos + [290 - l,0], ping, [20,20], [1,1,1], 1.0f, DRAWFLAG_ADDITIVE);
pos[1] += 20;
}
drawfont = FONT_CON;
}
void
Scores_Draw(void)
{
vector pos;
player pl;
pl = (player)pSeat->m_ePlayer;
if (autocvar_cl_centerscores) {
int c = 10;
/* calculate all valid entries */
for (int i = -1; i > -32; i--) {
if (getplayerkeyvalue(i, "name") && getplayerkeyfloat(i, "*spec") != 1) {
c += 10;
}
}
c += (serverkeyfloat("teams") * 10);
pos = video_mins + [(video_res[0] / 2) - 145, (video_res[1] / 2) - c];
} else {
pos = video_mins + [(video_res[0] / 2) - 145, 30];
}
if (serverkeyfloat("teams") > 0) {
Scores_DrawTeam(pl, pos);
} else {
Scores_DrawNormal(pl, pos);
}
}

48
src/client/seatlocal.h Normal file
View file

@ -0,0 +1,48 @@
// Moved here for better control of compile order.
// Must be aware of the ClientInfo_t struct, which in turn must be
// aware of some weapon-related structs before this point is reached
// in compilation.
struct
{
// PENDING - probably safe to remove these
int m_iHealthOld;
float m_flHealthAlpha;
int m_iArmorOld;
float m_flArmorAlpha;
int m_iAmmo1Old;
float m_flAmmo1Alpha;
int m_iAmmo2Old;
float m_flAmmo2Alpha;
int m_iAmmo3Old;
float m_flAmmo3Alpha;
int m_iPickupWeapon;
float m_flPickupAlpha;
int m_iHUDWeaponSelected;
float m_flHUDWeaponSelectTime;
float m_flPrevVGUI;
BOOL m_bNeedPrimaryRelease;
float m_flReleaseTime;
//TAGGG - assuming this is a fine place to put this
// It's the slower movement from holding shift down.
int iInputSpeed;
// Keeping for now, remove later
float fVGUI_Display;
// like pSeat->ePlayer, but already casted to the gamemod's custom "player" class.
// WARNING: Not yet set, find some place around the start of each frame where it
// makes sense to set this.
// Nevermind, do-able but likely won't be used much. Do m_clientinfo instead
//player m_ePlayerRef;
ClientInfo_t m_clientinfo;
} g_seatslocal[4], *pSeatLocal;
void pSeatLocal_init(void);

12
src/client/seatlocal.qc Normal file
View file

@ -0,0 +1,12 @@
// assumes pSeatLocal is set appropriately in advance.
// Would send something of that struct's type, but... looks like that isn't really
// an option. Like "structType* arg_this".
void
pSeatLocal_init(void)
{
pSeatLocal->m_flPrevVGUI = VGUI_SCREEN::NONE;
pSeatLocal->m_bNeedPrimaryRelease = FALSE;
pSeatLocal->m_flReleaseTime = 0;
}

View file

@ -0,0 +1,8 @@
// Just to be aware of these for earlier compiled things to be able to
// have some control.
void gFun_UI_EventGrabber_Initialize(void);
void gFun_UI_EventGrabber_Show(void);
void gFun_UI_EventGrabber_Hide(void);

View file

@ -0,0 +1,88 @@
// This file is named "vgui_late" for being compiled after the nuclide "src" project
// includes so that CUIWidget (src/vgui/ui.qc) is available.
// example of VGUI usage: see FreeCS, mention of this:
// winChooseTeam
// Simple UIWidget to absorb mouse-click and key-press events while in
// the MoTD or the buysidemenu screens.
// Do not give me child elements! Behavior for handling children removed.
class CUIEventGrabber:CUIWidget
{
//void(void) CUIEventGrabber;
virtual void(void) Draw;
virtual void(float, float, float, float) Input;
};
void
CUIEventGrabber::Draw(void)
{
// nothing!
}
//TODO: globals from src/client/defs.h over here now (like TS_mouseClickRef, etc.)
void
CUIEventGrabber::Input(float flEVType, float flKey, float flChar, float flDevID)
{
// no need to do this.
//g_vguiWidgetCount++;
switch (flEVType) {
case IE_KEYDOWN:
//printf("CUIEventGrabber::Input %d\n", flKey);
if (flKey == K_MOUSE1) {
TS_mouseClickRef = 1;
} else {
TS_keyRefDown = 1;
}
TS_keyRefUp = flKey;
TS_keyRefTapped = flKey; //lasts only this frame
TS_keyRefUpASCII = flChar;
break;
case IE_KEYUP:
if (flKey == K_MOUSE1) {
TS_mouseClickRef = 0;
} else {
TS_keyRefDown = 0;
}
TS_keyRefUp = 0;
TS_keyRefUpASCII = 0;
break;
}
}
// Could include this a lot earlier, but no point as it's useless without being
// able to call methods of it until CUIEventGrabber is defined (which requires
// UIWidget, which requires all nuclide stuff to be included, which happens late
// into the compile)
var CUIEventGrabber g_UI_EventGrabber;
void gFun_UI_EventGrabber_Initialize(void){
g_UI_EventGrabber = spawn(CUIEventGrabber);
g_uiDesktop.Add(g_UI_EventGrabber);
// make it "visible", or rather, "active" for grabbing input events?
// If this flag is missing it gets overlooked in ui.qc's run-through
// of child elements for sending input events to is skipped.
// Actually not even here, let calls to _Show or _Hide (below) handle this.
//g_UI_EventGrabber.m_iFlags |= 1;
//g_UI_EventGrabber.FlagAdd(1);
}
void gFun_UI_EventGrabber_Show(void){
g_UI_EventGrabber.FlagAdd(1);
}
void gFun_UI_EventGrabber_Hide(void){
g_UI_EventGrabber.FlagRemove(1);
}

114
src/client/util.h Normal file
View file

@ -0,0 +1,114 @@
#ifndef TS_CLIENT_UTIL_H
#define TS_CLIENT_UTIL_H
#define BITS_DIGITOPT_NONE 0
#define BITS_DIGITOPT_FILLER0 1
//Give filler 0's in front of the largest used digit if there were leftover digits
#define BITS_DIGITOPT_FILLERPAD 2
//Give filler padding (just space) in front of the largest used digit if there were leftover digits
// *** NOTICE - FILLER0 and FILLERPAD are incompatible. Use neither or either.
#define BITS_DIGITOPT_DEFAULT BITS_DIGITOPT_FILLERPAD
// space between the quote-pairs or not, and it concatenates it.
#define CREATE_imageFileRef_t(arg_newVarName, arg_sFilePath, arg_w, arg_h) imageFileRef_t arg_newVarName = { arg_sFilePath"_0.tga" , arg_w, arg_h};
//New version that does not do the "_0.tga" addition.
// We may want to use straight .tga files sometimes, which shouldn't get this change.
#define CREATE_imageFileRef_raw_t(arg_newVarName, arg_sFilePath, arg_w, arg_h) imageFileRef_t arg_newVarName = { arg_sFilePath, arg_w, arg_h};
#define CREATE_imageCropBounds_t(arg_newVarName, arg_imageFileRef, arg_srcPos_x, arg_srcPos_y, arg_srcSiz_x, arg_srcSiz_y) imageCropBounds_t arg_newVarName = {arg_imageFileRef, [arg_srcSiz_x, arg_srcSiz_y], [arg_srcPos_x/arg_imageFileRef.w, arg_srcPos_y/arg_imageFileRef.h], [arg_srcSiz_x/arg_imageFileRef.w, arg_srcSiz_y/arg_imageFileRef.h]};
//NO arg_vDrawScale. would have come after "arg_vDrawPos". A version that supports scaling can be done if needed.
//#define DRAW_IMAGE_BOUNDS_ADDITIVE(arg_sImageFileName, arg_imgBoundsVarName, arg_vDrawPos, arg_RGB, arg_alpha)
#define DRAW_IMAGE_CROPPED_ADDITIVE(arg_cropBoundsVarName, arg_vDrawPos, arg_RGB, arg_alpha) drawsubpic(arg_vDrawPos, arg_cropBoundsVarName.vSize, arg_cropBoundsVarName.myImageFileRef.sFilePath, arg_cropBoundsVarName.vCropStart, arg_cropBoundsVarName.vCropSize, arg_RGB, arg_alpha, DRAWFLAG_ADDITIVE);
//And a simple version
//scale not supported yet - can add that if needed.
#define DRAW_IMAGE_ADDITIVE(arg_imageFileRefVarName, arg_vDrawPos, arg_clr, arg_alpha) drawsubpic(arg_vDrawPos, [arg_imageFileRefVarName.w, arg_imageFileRefVarName.h], arg_imageFileRefVarName.sFilePath, [0, 0], [1, 1], arg_clr, arg_alpha, DRAWFLAG_ADDITIVE);
#define DRAW_IMAGE_NORMAL(arg_imageFileRefVarName, arg_vDrawPos, arg_clr, arg_alpha) drawsubpic(arg_vDrawPos, [arg_imageFileRefVarName.w, arg_imageFileRefVarName.h], arg_imageFileRefVarName.sFilePath, [0, 0], [1, 1], arg_clr, arg_alpha, DRAWFLAG_NORMAL);
#define DRAW_IMAGE_EXPER(arg_imageFileRefVarName, arg_vDrawPos, arg_vDrawPivot, flAngle, vScale, vRGB, flOpac) Gfx_RotScalePic(arg_imageFileRefVarName.sFilePath, arg_vDrawPos, arg_vDrawPivot, [arg_imageFileRefVarName.w, arg_imageFileRefVarName.h], flAngle, vScale, vRGB, flOpac)
#define DRAW_IMAGE_EXPER2(arg_imageFileRefVarName, arg_vDrawPos, vScale, vInset, vRGB, flOpac) Gfx_ScalePicPreserveBounds(arg_imageFileRefVarName.sFilePath, arg_vDrawPos, [arg_imageFileRefVarName.w, arg_imageFileRefVarName.h], vScale, vInset, vRGB, flOpac)
#if defined(CSQC) || defined(MENU)
//TAGGG - just give those bits names
#define DRAWTEXTFIELD_ALIGN_LEFT 1
#define DRAWTEXTFIELD_ALIGN_TOP 2
#define DRAWTEXTFIELD_ALIGN_RIGHT 4
#define DRAWTEXTFIELD_ALIGN_BOTTOM 8
//Use below to get an intention in one constant.
#define DRAWTEXTFIELD_ALIGN_CENTER_CENTER 0
#define DRAWTEXTFIELD_ALIGN_LEFT_CENTER 1
#define DRAWTEXTFIELD_ALIGN_LEFT_TOP 1|2
#define DRAWTEXTFIELD_ALIGN_CENTER_TOP 2
#define DRAWTEXTFIELD_ALIGN_RIGHT_TOP 4|2
#define DRAWTEXTFIELD_ALIGN_RIGHT_CENTER 4
#define DRAWTEXTFIELD_ALIGN_RIGHT_BOTTOM 4|8
#define DRAWTEXTFIELD_ALIGN_CENTER_BOTTOM 8
#define DRAWTEXTFIELD_ALIGN_LEFT_BOTTOM 1|8
#endif
typedef struct{
string sFilePath;
float w;
float h;
} imageFileRef_t;
typedef struct{
imageFileRef_t myImageFileRef;
vector vSize;
//NOTICE - vCropStart and vCropSize are fractions (decimals, between 0 and 1) that
// show what portion of the picked image to crop from.
// uhh... somewhere this decision may make sense?
vector vCropStart;
vector vCropSize;
/*
float x;
float y;
float w;
float h;
*/
} imageCropBounds_t;
//easy ref.
extern const vector vecZero;
int drawSpriteNumber(imageCropBounds_t* arg_ary_spriteSet, int arg_draw_x, int arg_draw_y, int arg_quantityToDraw, int arg_digits, int arg_bits, vector arg_clr, float arg_opac);
void Gfx_Text(vector vPos, string sText, vector vSize, vector vRGB, float flAlpha, float flDrawFlag, float flFont);
void Gfx_TextLineWrap( vector vPos, vector vSize, float fAlignFlags, string sText, float flFont );
void Gfx_RotScalePic( string sImage, vector arg_vDrawPos, vector arg_vDrawPivot, vector vSize, float flAngle, vector vScale, vector vRGB, float flOpac );
void Gfx_ScalePicPreserveBounds(string sImage, vector arg_vDrawPos, vector vSize, vector vScale, vector vInset, vector vRGB, float flOpac);
#endif // TS_CLIENT_UTIL_H

186
src/client/util.qc Normal file
View file

@ -0,0 +1,186 @@
//err. what?
//Then how do I supply a list of things to draw from?
//use another struct that itself has the array? well ok.
// OR make it a pointer and assume its length (or send it separately if needed... in this case
// we know it has to have 10 images: 1 for each of the digits, 0 - 9)
//../ts/client/vgui_buysidemenu.c:1745: error: Array arguments are not supported
// TODO - make this a more general utility for any UI, not just the inventory-related stuff.
//Now supports returning the number of pixels of width of the string it drew for nearby UI to see too.
int
drawSpriteNumber(imageCropBounds_t* arg_ary_spriteSet, int arg_draw_x, int arg_draw_y, int arg_quantityToDraw, int arg_digits, int arg_bits, vector arg_clr, float arg_opac){
//The "arg_bits" can give extra options about drawing. "0" means straight defaults.
//NONE SUPPORTED YET. If so, give enum like
//currently looking at leading 0's (whether they are drawn or not). The first non-zero digit stops this.
BOOLEAN leadingZeros = TRUE;
int spriteWidth = arg_ary_spriteSet[0].vSize.x;
int sprigeHeight = arg_ary_spriteSet[0].vSize.y;
int quantityYet = arg_quantityToDraw;
//if(arg_digits != -1){
int maxPossible = pow(10, arg_digits) - 1; //...9999, 999, 99, 9
if(quantityYet < 0){
//negatives not allowed, no need seen yet.
//To support negatives, we could draw a negative sign left of the highest digit place possible or
//of the drawn digit (subjective) and force "quantityYet" positive to draw it as expected.
quantityYet = 0;
}else if(quantityYet > maxPossible){
// force it to the max, that's all we can display
//quantityYet = maxPossible;
// ACTUALLY, go ahead and show this larger number anyway.
// Just take each additional digit necessary and push everything to the right.
// That is, if we planned on drawing 3 digits, but have to render the number 3600,
// and are told to draw it at draw_x = 300, we may start drawing it at x=280 instead to
// reach the same right point
string tempString = itos(quantityYet);
int digitsNeeded = (int)strlen(tempString);
if(digitsNeeded > arg_digits){
arg_draw_x -= (digitsNeeded - arg_digits)*spriteWidth;
arg_digits = digitsNeeded;
}
}
//}else{
// //no max constraint, but still no negatives yet.
// if(quantityYet < 0){
// quantityYet = 0;
// }
//}
int leftChange = 0;
int digitCount = 0;
int totalDrawWidth = 0;
vector vDrawPosYet;
for(int i = arg_digits; i >= 1; i--){
vDrawPosYet.x = arg_draw_x + (arg_digits - i) * spriteWidth + leftChange;
vDrawPosYet.y = arg_draw_y + 0;
//pow(10, i) - 1; //...9999, 999, 99, 9
//pow(10, i-1); //...1000, 100, 10, 1
//pow(10, -(i-1)); //...0.001, 0.01, 0.1, 1
float multi = pow(10, -(i - 1));
float bigMulti = pow(10, (i - 1));
int digitAtPlace = (int)(quantityYet * multi);
if(leadingZeros == TRUE && digitAtPlace == 0 && i > 1){
//another leading 0? this is a 0? Not the 1st digit?
if(arg_bits & BITS_DIGITOPT_FILLER0){
//still draw it here.
//example, for drawing a value of 872 with 5 digits allowed:
// 00872
DRAW_IMAGE_CROPPED_ADDITIVE(arg_ary_spriteSet[digitAtPlace], vDrawPosYet, arg_clr, arg_opac)
totalDrawWidth += spriteWidth;
}else if(arg_bits & BITS_DIGITOPT_FILLERPAD){
//this means, still skip over this space as though a filler digit were here.
//example from above (5 digits):
// 872 (two spaces for the unused 2 digits)
//(this behavior is natural; nothing special happens)
}else{
//no filler0, no fillerPad? Then skip over the space for unused digits.
leftChange -= spriteWidth;
//example from above:
// 872
}
}else{
//First non-zero digit makes leadingZeros FALSE for the first time.
//Also, the very first digit must be drawn unconditionally. Even if 0.
//All digits after this will be drawn.
leadingZeros = FALSE;
//draw digitAtPlace.
DRAW_IMAGE_CROPPED_ADDITIVE(arg_ary_spriteSet[digitAtPlace], vDrawPosYet, arg_clr, arg_opac)
totalDrawWidth += spriteWidth;
}
quantityYet = quantityYet - digitAtPlace*bigMulti;
}
//Not functional, but "quantityYet" should end up as "0" all the time.
return totalDrawWidth;
}//END OF drawSpriteNumber
//TAGGG - BOTH NEW. Convenience methods for drawing without any font-related struct, just some font's load ID.
// Otherwise close to the engine-provided drawstring and drawtextfield methods.
// "drawfont" is a global provided by the engine (fteextensions.qc)
// Compare with vgui/font.cpp's "Font_DrawText" and "Font_DrawField" which take a font struct.
void
Gfx_Text(vector vPos, string sText, vector vSize, vector vRGB, float flAlpha, float flDrawFlag, float flFont)
{
drawfont = flFont;
drawstring(vPos, sText, vSize, vRGB, flAlpha, flDrawFlag);
}
void
Gfx_TextLineWrap( vector vPos, vector vSize, float fAlignFlags, string sText, float flFont )
{
drawfont = flFont;
drawtextfield( vPos, vSize, fAlignFlags, sText );
}
//TAGGG - "Gfx_RotScalePic" was made from a blend of methods found in The Wastes.
void
Gfx_RotScalePic( string sImage, vector arg_vDrawPos, vector arg_vDrawPivot, vector vSize, float flAngle, vector vScale, vector vRGB, float flOpac )
{
//vector mins = [0, 0];
vector mins =
[
(-arg_vDrawPivot[0] + -vSize[0]/2) * vScale[0],
(-arg_vDrawPivot[1] + -vSize[1]/2) * vScale[1]
];
vector maxs =
[
(-arg_vDrawPivot[0] + vSize[0]/2) * vScale[0],
(-arg_vDrawPivot[1] + vSize[1]/2) * vScale[1]
];
//printfline("Gfx_RotScalePic - %.0f %.0f, %.0f %.0f", mins[0], mins[1], maxs[0], maxs[1]);
vector vBaseSize = vSize;
//Gfx_Pic( vPos + vScalePos, sImage, vScaleSize, vRGB, flAlpha, 0 );
drawrotpic(arg_vDrawPos + [arg_vDrawPivot[0]*vScale[0], arg_vDrawPivot[1]*vScale[1]], mins, maxs, sImage, vRGB, flOpac, flAngle);
// (time * 40) % 360
}
void
Gfx_ScalePicPreserveBounds(string sImage, vector arg_vDrawPos, vector vSize, vector vScale, vector vInset, vector vRGB, float flOpac)
{
vector srcpos = [vInset[0], vInset[1]];
vector srcsz;
vector sz;
//srcsz = vSize;
srcsz[0] = 1.0 - (vInset[0]*2) ;
srcsz[1] = 1.0 - (vInset[1]*2);
sz[0] = vSize[0] * vScale[0];
sz[1] = vSize[1] * vScale[1];
vector vecOff;// = [0,0];
vecOff[0] = -sz[0]/2;
vecOff[1] = -sz[1]/2;
// drawsubpic(pos, sz, pic, srcpos, srcsz, rgb, alpha, 0);
drawsubpic(arg_vDrawPos + vecOff, sz, sImage, srcpos, srcsz, vRGB, flOpac); //, 0
}

50
src/client/vgui.h Normal file
View file

@ -0,0 +1,50 @@
#define VGUI_WINDOW_BGCOLOR '0.0 0.0 0.0'
#define VGUI_WINDOW_FGCOLOR '1.0 0.5 0.0'
#define VGUI_WINDOW_BGALPHA 0.76
#define VGUI_WINDOW_FGALPHA 1.0
//var int iVGUIKey;
vector vVGUIWindowPos;
vector vVGUIWindowSiz;
//vector vVGUIButtonPos;
string sMOTDString[25];
string sMapString[35];
var string sMOTD_total;
class player;
// Keep in synch with the vguiMenus array of vgui.c
enum VGUI_SCREEN{
NONE = 0,
MOTD,
BUYSIDEMENU
};
typedef struct {
string sTitle;
// Whether to do a VGUI_Window call to draw a window.
// Not very customizable for now, intended only for the MoTD, but adapts to different screen sizes.
// Could have other settings added later, or even be handled per screen's draw call too for completely re-doing
BOOLEAN fDrawMainWindowAuto;
// Custom draw script for a particular screen choice
//TAGGG - now accepts how much to change the font size (and adjust other things) by.
// Also accepts the player for getting other info from.
void(player arg_player, vector vPos, vector vWindowSiz, float fFontSizeMulti ) vDraw;
// What to do the moment the screen is changed to this.
void(void) vOnInit;
} vguiwindow_t;
void CSQC_VGUI_Init(void);
float CSQC_VGUI_Draw( player arg_player);
void VGUI_ChangeScreen(VGUI_SCREEN fNewScreenID);

247
src/client/vgui.qc Normal file
View file

@ -0,0 +1,247 @@
/***
*
* Copyright (c) 2016-2019 Marco 'eukara' Hladik. All rights reserved.
*
* See the file LICENSE attached with the sources for usage details.
*
****/
// Menus with their window titles and draw functions
//TAGGG
// also public now so that the first MOTD element can have its sTitle modified by game
// logic. It's a bit more than just one language key now.
// And why did the array indeces have a number like this: "[11]"? Looks to adjust fine
// to having no number and doing the count itself.
// where does "VGUI_TITLE_MODT" or the text it produces ("Message of the day") ever occur
// in game files or script?
// ANSWERED: it comes from the language file. See one of the files near the compiled
// .dat file
// (typically in the game's data.pk3dir fodlder) like so:
// csprogs.dat.en.po
// The identifier and value for that language will show up.
// In this case, VGUI_TITLE_MODT won't be used. This new way mimicks the original The
// Specialists initial screen a little more.
// We can't use logic stuff in a global scope like this. This (vguiMenus[0].sTitle) can
// be set in an init method instead.
// Keep in synch with vgui.h's VGUI_SCREEN enum choices, besides the NONE choice.
// That isn't represented, not even by a dummy.
var vguiwindow_t vguiMenus[] = {
//{ _("VGUI_TITLE_MOTD"), VGUI_MessageOfTheDay },
{ "", TRUE, VGUI_MessageOfTheDay, NULL },
{ "", FALSE, VGUI_BuySideMenu_Update, VGUI_BuySideMenu_OnInit}
};
//var float nextPrintoutTime = -1;
void
VGUI_ChangeScreen(VGUI_SCREEN arg_NewScreenID)
{
pSeatLocal->fVGUI_Display = (float)arg_NewScreenID;
if(arg_NewScreenID <= VGUI_SCREEN::NONE){
// If at NONE or below, also do nothing. This has no "vOnInit" behavior.
// Besides obligatory cleanup if we choose (which may as well be done right here)
// And turn the cursor lock off.
setcursormode(FALSE, "gfx/cursor", [0,0,0], 1.0f);
// And don't let Nuclide's src/client/entry.qc try to re-lock the mouse!
gFun_UI_EventGrabber_Hide();
return;
}
// If this screen has a vOnInit, do it!
// This is done for the new screen once as soon as it becomes the active one.
// Also, turn the cursor lock on, clearly this needs user input.
// src/client/entry.qc already has this check too for having any UI widget, but
// it takes one mouse-movement for it to kick-in, which causes one slight nudge of the
// camera before the cursor appears. Not terrible, but calling for the lock this early
// ensures that doesn't get a chance to happen.
setcursormode(TRUE, "gfx/cursor", [0,0,0], 1.0f);
// And let the event grabber be shown so that entry.qc doesn't try to undo the mouse lock.
gFun_UI_EventGrabber_Show();
if(vguiMenus[arg_NewScreenID - 1].vOnInit != NULL){
vguiMenus[arg_NewScreenID - 1].vOnInit();
}
}//changeScreen
/*
=================
CSQC_VGUI_Draw
This is the entry point for FreeTS's (cloned from FreeCS) own "VGUI" implementation
Run every frame
=================
*/
float
CSQC_VGUI_Draw( player arg_player)
{
if ( pSeatLocal->fVGUI_Display == VGUI_SCREEN::NONE ) {
setcursormode( FALSE );
return FALSE;
}
//int geh = vguiMenus.length;
//vTextPos[1] += 14;
float fontSizeMulti;
// How much space to add to the left and right of the drawn windows (x) and to the top and
// bottom (y).
// That does mean each removes twice its value of the drawn window in that dimension.
// ex: a window_pad_x of 60 will actually remove 120 from the drawn window. 60 from the
// left, 60 from the right.
float window_pad_x = video_res[0] * 0.175;
float window_pad_y = video_res[1] * 0.166;
float window_width_x = video_res[0] - window_pad_x*2;
float window_height_y = video_res[1] - window_pad_y*2;
// If the height is over 800, stay at full size. It only gets smaller as the height
// decreases.
if(video_res[1] >= 800){
fontSizeMulti = 1 * 1.0; //REMOVE 1.8 later!!!;
}else{
//scale it to fit the screen size.
//fontSizeMulti = 0.2 + 0.001 * video_res[1];
//fontSizeMulti = 0.234 + 0.0007 * video_res[1];
fontSizeMulti = 0.1429 + 0.001 * 1.0 * video_res[1];
}//END OF screen height check
//
if(FONT_ARIAL == -1 || Recorded_video_res != video_res){
//Give it a font based on this screen size.
//Recorded_video_res = video_res;
Recorded_video_res[0] = video_res[0];
Recorded_video_res[1] = video_res[1];
//FONT_ARIAL = loadfont( "label", "arial", "32", -1 );
float font_arial_size = (fontSizeMulti * 24);
float font_arial_title_size = (fontSizeMulti * 32);
string str_font_arial_size = ftos(font_arial_size);
string str_font_arial_title_size = ftos(font_arial_title_size);
FONT_ARIAL = loadfont( "game", "arial", str_font_arial_size, -1 );
FONT_ARIAL_TITLE = loadfont( "game", "arial", str_font_arial_title_size, -1 );
//print( sprintf("CHANGE height:%i fontm:%.2f fontref:%i match:(%i, %i) totalmatch:%i\n", (int)video_res[1], fontSizeMulti, (int)FONT_ARIAL, (int)(Recorded_video_res[0]==video_res[0]), (int)(Recorded_video_res[1]==video_res[1]), (int)(Recorded_video_res==video_res)) );
}
//little demo.
/*
if(nextPrintoutTime == -1 || (time >= nextPrintoutTime) ){
nextPrintoutTime = time + 2;
print( sprintf("timed printout. height:%i fontm:%.2f fontref:%i match:(%i, %i) totalmatch:%i\n", (int)video_res[1], fontSizeMulti, (int)FONT_ARIAL, (int)(Recorded_video_res[0]==video_res[0]), (int)(Recorded_video_res[1]==video_res[1]), (int)(Recorded_video_res==video_res)) );
}
*/
vVGUIColor = autocvar_vgui_color * ( 1 / 255 );
// Align the window to the center
vVGUIWindowPos = video_mins;
//vVGUIWindowPos[0] += ( video_res[0] / 2 ) - 320;
//vVGUIWindowPos[1] += ( video_res[1] / 2 ) - 240;
vVGUIWindowPos[0] += window_pad_x;
vVGUIWindowPos[1] += window_pad_y;
vVGUIWindowSiz[0] = window_width_x;
vVGUIWindowSiz[1] = window_height_y;
// draw the window only if this screen says to.
if(vguiMenus[ pSeatLocal->fVGUI_Display - 1 ].fDrawMainWindowAuto){
VGUI_Window( vVGUIWindowPos, vVGUIWindowSiz, vguiMenus[ pSeatLocal->fVGUI_Display - 1 ].sTitle, [fontSizeMulti*32,fontSizeMulti*32] );
}
// Display the contents of whatever we have selected
vguiMenus[ pSeatLocal->fVGUI_Display - 1 ].vDraw( arg_player, vVGUIWindowPos, vVGUIWindowSiz, fontSizeMulti );
return TRUE;
}
/*
=================
CSQC_VGUI_Init
Initialize all there is
=================
*/
// NOTE! Not a built-in method, manually called by client init (ClientGame_Init).
// ALSO - this means once for the entire client, so handle all pSeat choices
// individually as it does.
void
CSQC_VGUI_Init(void)
{
string sTemp;
int iMOTDLength;
int i;
int s;
filestream fmMapDescr;
// only the first screen choice will use its 'sTitle'
vguiMenus[0].sTitle = sprintf("%s - %s", "Free Specialists", serverkey("hostname"));
// First load the MESSAGE OF THE DAY
// TODO: Move this to the server and put strings into infokeys
//sMOTD_total = serverkey("motd_total");
sMOTD_total = "";
iMOTDLength = stof( serverkey( "motdlength" ) );
for (i = 0; i < iMOTDLength; i++ ) {
sMOTDString[ i ] = serverkey( sprintf( "motdline%i", i ) );
if ( sMOTDString[ i ] == "/" ) {
sMOTD_total = strcat(sMOTD_total, "\n" );
}else{
sMOTD_total = strcat(sMOTD_total, sprintf("%s\n", sMOTDString[ i ]) );
}
}
// color it
// NOPE! Let this be handled elsewhere in case of a different VGUI color!
//sMOTD_total = strcat("^xFA0", sMOTD_total);
// Now load the MAP DESCRIPTION
fmMapDescr = fopen( sprintf( "maps/%s.txt", mapname ), FILE_READ );
if ( fmMapDescr != -1 ) {
for (i = 0; i < 35; i++ ) {
sTemp = fgets( fmMapDescr );
if not ( sTemp ) {
break;
}
sMapString[ i ] = sTemp;
}
fclose( fmMapDescr );
}
gFun_UI_EventGrabber_Initialize();
////////////////////////////////////////////////////
// FOR NOW, a lazy way of init for all seats.
// Apply to all seats, not worrying about what numclientseats is because this is tiny.
if (serverkeyfloat("slots") != 1) {
// We start on the MOTD, always
for (s = 0; s < g_seats.length; s++){
pSeat = &g_seats[s];
pSeatLocal = &g_seatslocal[s];
VGUI_ChangeScreen(VGUI_SCREEN::MOTD);
}
}else{
// make all pSeats start at the NONE screen instead
for (s = 0; s < g_seats.length; s++){
pSeat = &g_seats[s];
pSeatLocal = &g_seatslocal[s];
VGUI_ChangeScreen(VGUI_SCREEN::NONE);
}
}
}

View file

@ -0,0 +1 @@

File diff suppressed because it is too large Load diff

100
src/client/vgui_motd.qc Normal file
View file

@ -0,0 +1,100 @@
/***
*
* Copyright (c) 2016-2019 Marco 'eukara' Hladik. All rights reserved.
*
* See the file LICENSE attached with the sources for usage details.
*
****/
/*
====================
VGUI_MessageOfTheDay
The MOTD screen.
TODO: Networking still needs to be done.
You can't store motds in infokey strings because
newline chars are not supported. You could hack it to use
an array of infokeys, but that'll clutter things up
====================
*/
// Cloned from src/menu-fn/w_label.qc, HACKY
string
Colors_RGB8_to_HEX(vector color)
{
string out = "^x";
//string out = "";
for (int i = 0; i < 3; i++) {
string a = "";
float b = rint(color[i] * 15);
switch (b) {
case 10:
a = "A";
break;
case 11:
a = "B";
break;
case 12:
a = "C";
break;
case 13:
a = "D";
break;
case 14:
a = "E";
break;
case 15:
a = "F";
break;
default:
a = ftos(b);
}
out = sprintf("%s%s", out, a);
}
return out;
}
void VGUI_MessageOfTheDay(player arg_player, vector vPos, vector vWindowSiz, float fFontSizeMulti ) {
static void MessageOfTheDay_ButtonOK( void ) {
//pSeatLocal->fVGUI_Display = VGUI_SCREEN::BUYSIDEMENU;
VGUI_ChangeScreen(VGUI_SCREEN::BUYSIDEMENU);
}
//TAGGG - start from this location instead.
//vector vTextPos = vPos + '16 116 0';
vector vTextPos = vPos + '16 64 0';
// apply the VGUI color, since TextLineWrap (route to FTE method drawtextfield)
// does not offer color, but in-text markup can offer this.
// PENDING: if this is done a lot, offer it as a utility method?
// That can take a color vector and apply this hex step for us.
// Just beware that this applies to all text after the affected text as well,
// markup only stops when overridden by other markup as read from left to right.
// example:
// ^x0F0 term1 term2
// ...colors term1 and term2 green, even if this was not intentional for term2.
// ^x0F0 term1 ^xF00 term2
// ...colors term1 green, but term2 red.
string tempText = sprintf("%s%s", Colors_RGB8_to_HEX(vVGUIColor), sMOTD_total);
// Oh, and no transparency it seems, so no slight show-through that the title text gets.
// So you have to invent your own wordwrap to get that?? Let's just skip that.
Gfx_TextLineWrap( vTextPos, vWindowSiz - ('16 64 0' * 2) , DRAWTEXTFIELD_ALIGN_LEFT_TOP, tempText, FONT_ARIAL );
if(TS_keyRefDown && TS_keyRefTapped == 13){
//pressing enter here works too.
MessageOfTheDay_ButtonOK();
TS_keyRefTapped = 0; //why do we have to do this, no clue
}
VGUI_Button( _("VGUI_OK"), MessageOfTheDay_ButtonOK, [vPos.x + 16, vPos.y + vWindowSiz.y - 30 - 16, 0], '100 30 0' );
}

202
src/client/vguiobjects.qc Normal file
View file

@ -0,0 +1,202 @@
/***
*
* Copyright (c) 2016-2019 Marco 'eukara' Hladik. All rights reserved.
*
* See the file LICENSE attached with the sources for usage details.
*
****/
/*
====================
VGUI_CheckMouse
Returns whether or not our mouse cursor hovers over a region
====================
*/
float VGUI_CheckMouse( vector vPos, vector vReg ) {
vector vSMins, vSMaxs;
vSMins = vPos;
vSMaxs = vPos;
vSMins[0] = vPos[0];
vSMaxs[1] = vPos[1] - 1;
vSMaxs[0] = vPos[0] + vReg[0];
vSMaxs[1] = vPos[1] + vReg[1];
if ( mouse_pos[0] >= vSMins[0] && mouse_pos[0] <= vSMaxs[0] ) {
if (mouse_pos[1] >= vSMins[1] && mouse_pos[1] <= vSMaxs[1] ) {
return 1;
}
}
return 0;
}
/*
====================
VGUI_Window
Draws window with outline, border and title
====================
*/
/*
//TAGGG - NEW. This is the old parameters. Sends to VGUI_WINDOW to draw the string with a default font size
// (the default font size was actually 16, it has been increased to 24)
// Order in the new overload further below also puts things in a different order.
inline void VGUI_Window( string sTitle, vector vPosition, vector vSize ) {
VGUI_Window(vPosition, vSize, sTitle, [24, 24]);
}
*/
void VGUI_Window( vector vPosition, vector vSize, string sTitle, vector vFontSize ) {
// Draw the background
drawfill( vPosition, vSize, VGUI_WINDOW_BGCOLOR, VGUI_WINDOW_BGALPHA );
// Sides
drawfill( vPosition, [vSize[0], 1], vVGUIColor, VGUI_WINDOW_FGALPHA );
drawfill( [vPosition[0], vPosition[1] + vSize[1] - 1], [vSize[0], 1], vVGUIColor, VGUI_WINDOW_FGALPHA );
drawfill( vPosition, [1, vSize[1]], vVGUIColor, VGUI_WINDOW_FGALPHA );
drawfill( [vPosition[0] + vSize[0] - 1, vPosition[1]], [1, vSize[1]], vVGUIColor, VGUI_WINDOW_FGALPHA );
// Draw the window title
//TAGGG - little bigger.
// Also use DRAWFLAG_NORMAL to stop the transparency from not being a solid color choice.
// ADDITIVE interprets any darker color than as bright as possible as transparency.
// The wordwrapped text does not support this, so let them both be solid.
// No benefit to transparent text anyway.
//title text font size depends on the height of the window.
//Gfx_Text( vPosition + '16 16', sTitle, '12 12', vVGUIColor, VGUI_WINDOW_FGALPHA, DRAWFLAG_ADDITIVE, FONT_CON );
Gfx_Text( vPosition + '16 16', sTitle, vFontSize, vVGUIColor, VGUI_WINDOW_FGALPHA, DRAWFLAG_NORMAL, FONT_ARIAL_TITLE );
//Gfx_TextLineWrap( vPosition + '16 16', vSize - ('16 64 0' * 2) , DRAWTEXTFIELD_ALIGN_LEFT_TOP, sTitle, FONT_ARIAL_STD );
drawfill( vPosition + '0 48', [vSize[0], 1], vVGUIColor, VGUI_WINDOW_FGALPHA );
}
/*
====================
VGUI_WindowSmall
Draws smaller window with outline, border and title
====================
*/
void VGUI_WindowSmall( string sTitle, vector vPosition, vector vSize ) {
// Draw the background
drawfill( vPosition, vSize, VGUI_WINDOW_BGCOLOR, VGUI_WINDOW_BGALPHA );
// Sides
drawfill( vPosition, [vSize[0], 1], vVGUIColor, VGUI_WINDOW_FGALPHA );
drawfill( [vPosition[0], vPosition[1] + vSize[1] - 1], [vSize[0], 1], vVGUIColor, VGUI_WINDOW_FGALPHA );
drawfill( vPosition, [1, vSize[1]], vVGUIColor, VGUI_WINDOW_FGALPHA );
drawfill( [vPosition[0] + vSize[0] - 1, vPosition[1]], [1, vSize[1]], vVGUIColor, VGUI_WINDOW_FGALPHA );
// Draw the window title
Gfx_Text( vPosition + '8 8', sTitle, '12 12', vVGUIColor, VGUI_WINDOW_FGALPHA, DRAWFLAG_ADDITIVE, FONT_CON );
drawfill( vPosition + '0 24', [vSize[0], 1], vVGUIColor, VGUI_WINDOW_FGALPHA );
}
/*
====================
VGUI_Button
Draws a button, returns whether or not a mouse is hovering over it (for inheritance' sake)
====================
*/
//*** NOTE! Buysidemenu buttons don't use this! Only the MoTD close button, at least so far.
float VGUI_Button( string sLabel, void() vFunction, vector vPosition, vector vSize) {
vector vLabelPos;
/*
if ( iVGUIKey < 57 ) {
iVGUIKey++;
}
*/
drawfill( vPosition, [vSize[0], 1], vVGUIColor, VGUI_WINDOW_FGALPHA );
drawfill( [vPosition[0], vPosition[1] + vSize[1] - 1], [vSize[0], 1], vVGUIColor, VGUI_WINDOW_FGALPHA );
drawfill( vPosition, [1, vSize[1]], vVGUIColor, VGUI_WINDOW_FGALPHA );
drawfill( [vPosition[0] + vSize[0] - 1, vPosition[1]], [1, vSize[1]], vVGUIColor, VGUI_WINDOW_FGALPHA );
// Draw the button label
vLabelPos[0] = vPosition[0] + 16;
vLabelPos[1] = vPosition[1] + ( ( vSize[1] / 2 ) - 4 );
/*
//TAGGG REPLACEMENT - "pSeatLocal->fInputKeyCode" for "TS_keyRefUp".
if ( ( iVGUIKey == TS_keyRefUp ) ) {
vFunction();
TS_keyRefUp = 0;
return TRUE;
}
*/
if ( VGUI_CheckMouse( vPosition, vSize ) ) {
//pSeatLocal->fVGUI_Display
if ( TS_mouseClickRef == TRUE ) {
vFunction();
TS_mouseClickRef = FALSE;
}
Gfx_Text( vLabelPos, sLabel, vButtonFontSize, vVGUIColor, VGUI_WINDOW_FGALPHA, DRAWFLAG_ADDITIVE, FONT_ARIAL_STD );
drawfill( vLabelPos + '0 10 0', [ stringwidth( sLabel, TRUE, '12 12' ), 1], vVGUIColor, VGUI_WINDOW_FGALPHA );
return TRUE;
} else {
Gfx_Text( vLabelPos, sLabel, vButtonFontSize, vVGUIColor * 0.8, VGUI_WINDOW_FGALPHA, DRAWFLAG_ADDITIVE, FONT_ARIAL_STD );
}
return FALSE;
}
/*
====================
VGUI_FakeButton
Looks like a button, doesn't function though. Meant for dead buttons
====================
*/
void VGUI_FakeButton( string sLabel, vector vPosition, vector vSize ) {
vector vLabelPos;
drawfill( vPosition, [vSize[0], 1], vVGUIColor, VGUI_WINDOW_FGALPHA );
drawfill( [vPosition[0], vPosition[1] + vSize[1] - 1], [vSize[0], 1], vVGUIColor, VGUI_WINDOW_FGALPHA );
drawfill( vPosition, [1, vSize[1]], vVGUIColor, VGUI_WINDOW_FGALPHA );
drawfill( [vPosition[0] + vSize[0] - 1, vPosition[1]], [1, vSize[1]], vVGUIColor, VGUI_WINDOW_FGALPHA );
// Draw the button label
vLabelPos[0] = vPosition[0] + 16;
vLabelPos[1] = vPosition[1] + ( ( vSize[1] / 2 ) - 4 );
Gfx_Text( vLabelPos, sLabel, '12 12', vVGUIColor * 0.5, VGUI_WINDOW_FGALPHA, DRAWFLAG_ADDITIVE, FONT_CON );
}
/*
====================
VGUI_Text
Wrapper for simple GUI text labels
====================
*/
void VGUI_Text( string sText, vector vPos, vector vSize, float fFont ) {
Gfx_Text( vPos, sText, vSize, vVGUIColor, VGUI_WINDOW_FGALPHA, DRAWFLAG_ADDITIVE, fFont );
}
/*
====================
VGUI_RightText
Right-aligned version of above
====================
*/
void VGUI_RightText( vector vPos, string sText, vector vSize, vector vColor, float fFont ) {
vPos[0] -= stringwidth( sText, FALSE, vSize );
Gfx_Text( vPos, sText, vSize, vColor, 1, 0, fFont );
}

12
src/client/view.h Normal file
View file

@ -0,0 +1,12 @@
void TS_SetViewModelFromStats(void);
void View_RoutineCheck(void);
void View_UpdateWeapon(entity vm, entity mflash);
void resetViewModel(void);
void View_HandleZoom(void);
vector View_ShakeCalculate(vector vecInputAngles);
void View_ShakeCreate(int iLevel);
void View_ShowMuzzleflash(int index);

380
src/client/view.qc Normal file
View file

@ -0,0 +1,380 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
// Also, Nuclide offers Weapons_SetGeomset for calling "setcustomskin" too, but one string
// at a time. Don't really know if that way or the current way (cumulative string for
// many commands delimited in one setcustomskin call) is any better, so leaving this as
// it is for now.
// NEW
void TS_SetViewModelFromStats(){
player pl = (player)pSeat->m_ePlayer;
entity vm = pSeat->m_eViewModel;
entity mflash = pSeat->m_eMuzzleflash;
weapondata_basic_t* basicP;
weapondynamic_t dynaRef;
printfline("TS_SetViewModelFromStats: I happen? activeweap:%d", pl.activeweapon);
basicP = pl.getEquippedWeaponData();
dynaRef = pl.ary_myWeapons[pl.inventoryEquippedIndex];
//TAGGG NOTE - we're not supporting skins apparently
//if (autocvar_skins_dir != "") {
// wm = sprintf("skins/%s/%s", autocvar_skins_dir, sViewModels[ aw - 1 ]);
//} else {
// wm = sprintf("models/%s", sViewModels[ aw - 1 ]);
//}
//TAGGG NOTE - we're not using that "sViewModels" string either.
// Grab the currently equipped weapon, its weapon info, and from there pull the viewmodel to use
weapondata_basic_t* basicPointer = (weapondata_basic_t*) ary_weaponData[dynaRef.weaponID];
weapondata_basic_t basicRef = *(basicPointer);
Weapons_SetModel((*basicP).sViewModelPath);
string cumulativeCommandString = "";
// If this weapon is a Gun (or ironsight-able), check for the presence of attachment-giving
// buyopts.
if(basicRef.typeID == WEAPONDATA_TYPEID_GUN || basicRef.typeID == WEAPONDATA_TYPEID_IRONSIGHT){
weapondata_gun_t gunRef = *((weapondata_gun_t*)basicPointer);
if(dynaRef.iBitsUpgrade & BITS_WEAPONOPT_SILENCER){
// has the silencer? and an attachment?
if(gunRef.silencer_part != -1){
cumulativeCommandString = sprintf("%sgeomset %i 2\n", cumulativeCommandString, gunRef.silencer_part );
}
}
if(dynaRef.iBitsUpgrade & BITS_WEAPONOPT_LASERSIGHT){
// has the silencer? and an attachment?
if(gunRef.lasersight_part != -1){
cumulativeCommandString = sprintf("%sgeomset %i 2\n", cumulativeCommandString, gunRef.lasersight_part );
}
}
if(dynaRef.iBitsUpgrade & BITS_WEAPONOPT_FLASHLIGHT){
// has the silencer? and an attachment?
if(gunRef.flashlight_part != -1){
cumulativeCommandString = sprintf("%sgeomset %i 2\n", cumulativeCommandString, gunRef.flashlight_part );
}
}
if(dynaRef.iBitsUpgrade & BITS_WEAPONOPT_SCOPE){
// has the silencer? and an attachment?
if(gunRef.scope_part != -1){
cumulativeCommandString = sprintf("%sgeomset %i 2\n", cumulativeCommandString, gunRef.scope_part );
}
}
}//END OF gun type check (is a gun of some sort)
if(dynaRef.forceBodygroup1Submodel > 0){
cumulativeCommandString = sprintf("%sgeomset 0 %i\n", cumulativeCommandString, dynaRef.forceBodygroup1Submodel );
}
// no need to do that geomset with the same value again.
pl.prev_forceBodygroup1Submodel = dynaRef.forceBodygroup1Submodel;
setcustomskin(vm, "", cumulativeCommandString);
// leftovers following a draw call, that likely lead here to begin with.
SAVE_STATE(pl.w_attack_next);
SAVE_STATE(pl.w_idle_next);
SAVE_STATE(pl.viewzoom);
SAVE_STATE(pl.weapontime);
skel_delete( mflash.skeletonindex );
mflash.skeletonindex = skel_create( vm.modelindex );
pSeat->m_iVMBones = skel_get_numbones( mflash.skeletonindex ) + 1;
// Don't let View_UpdateWeapon do this all over.
pSeat->m_iLastWeapon = pl.activeweapon;
}//TS_SetViewModelFromStats
// On any frame, see if something about the current viewmodel needs to be changed
void View_RoutineCheck(void){
player pl = (player)pSeat->m_ePlayer;
entity vm = pSeat->m_eViewModel;
weapondynamic_t dynaRef;
// That can happen, apparently.
if(pl.inventoryEquippedIndex == -1){
return;
}
dynaRef = pl.ary_myWeapons[pl.inventoryEquippedIndex];
if(pl.prev_forceBodygroup1Submodel != dynaRef.forceBodygroup1Submodel){
if(dynaRef.forceBodygroup1Submodel > 0){
string commandString;
commandString = sprintf("geomset 0 %i\n", dynaRef.forceBodygroup1Submodel );
setcustomskin(vm, "", commandString);
}
pl.prev_forceBodygroup1Submodel = dynaRef.forceBodygroup1Submodel;
}
}//View_RoutineCheck
// vm is pSeat->m_eViewModel passed along from Nuclide's View_DrawViewModel
void
View_UpdateWeapon(entity vm, entity mflash)
{
player pl = (player)pSeat->m_ePlayer;
/* only bother upon change */
if (pSeat->m_iLastWeapon == pl.activeweapon) {
View_RoutineCheck();
return;
}
printfline("View_UpdateWeapon: change detected: %i -> %d", pSeat->m_iLastWeapon, pl.activeweapon);
pSeat->m_iOldWeapon = pSeat->m_iLastWeapon;
pSeat->m_iLastWeapon = pl.activeweapon;
if (!pl.activeweapon /*|| pl.inventoryEquippedIndex < 0*/) {
// can't work with this!
resetViewModel();
return;
}
//printfline("View_UpdateWeapon: change: %d vs %d", pSeat->m_iLastWeapon, pl.activeweapon);
// Call this to do a few other things for any weapon change
TS_Weapon_Draw_extra();
/* hack, we changed the wep, move this into Game_Input/PMove */
Weapons_Draw();
//No longer a var! Oops
//pSeat->m_iVMEjectBone = pSeat->m_iVMBones + 1;
/* we forced a weapon call outside the prediction,
* thus we need to update all the net variables to
* make sure these updates are recognized. this is
* vile but it'll have to do for now */
SAVE_STATE(pl.w_attack_next);
SAVE_STATE(pl.w_idle_next);
SAVE_STATE(pl.viewzoom);
SAVE_STATE(pl.weapontime);
//TAGGG - NEW VAR.
SAVE_STATE(pl.w_attack_akimbo_next);
skel_delete( mflash.skeletonindex );
mflash.skeletonindex = skel_create( vm.modelindex );
pSeat->m_iVMBones = skel_get_numbones( mflash.skeletonindex ) + 1;
}
//TAGGG - NEW METHODS
void
resetViewModel(void)
{
//TAGGG - CRITICAL. NOT ANYMORE!!!
//return;
printfline("resetViewModel called.");
if(pSeat->m_ePlayer == NULL){
printfline("resetViewModel: early end #1, client ent NULL");
return;
}
if(pSeat->m_ePlayer.classname != "player"){
printfline("resetViewModel: early end #2, client ent not player");
return;
}
//was m_eViewModel and m_eMuzzleflash
entity vm = pSeat->m_eViewModel;
entity mflash = pSeat->m_eMuzzleflash;
setmodel( vm, "" );
skel_delete( mflash.skeletonindex );
mflash.skeletonindex = 0; //er wat. would -1 be better or not?
pSeat->m_iVMBones = 0 + 1;
//pSeat->m_iVMEjectBone = pSeat->m_iVMBones + 1;
/*
player pl = (player) pSeat->m_ePlayer;
pl.setInventoryEquippedIndex(-1); //do we have to?
*/
}//END OF resetViewModel
//TAGGG - loaned from The Wastes.
void View_HandleZoom(void){
player pl = (player)pSeat->m_ePlayer;
if(pl == NULL){
return;
}
/*
if(getplayerkeyvalue(player_localnum, "*spec") != "0"){
//TAGGG - CRITICAL.
// WAIT! This is a spectator, so...? can we even trust pSeat->ePlayer to be a player?
// Confusing.
// That leads me to believe that either zoom-related vars should be
// pSeat-><clienttdata> - specific, or not handled here at all.
// If our FOV was anything but 1, force it so.
// Spectator mode instantly forgets any scope-related FOV mods.
// Nuclide might not be calling setsensitivityscaler, so doing it here.
// No problem if the setproperty is a little redundant as that might be happening.
if(pl.flZoomLevel < 1.0 || pl.viewzoom < 1.0){
pl.flZoomLerp = 0;
pl.flZoomLevel = 1;
pl.viewzoom = 1;
setproperty(VF_AFOV, cvar("fov") * pl.flZoomLevel);
setsensitivityscaler(pl.flZoomLevel);
}
// No further zoom logic
return;
}
*/
//TAGGG - any references to STAT_VIEWZOOM are now garbage.
// Rely on pl.flTargetZoom instead, it's sent to the client every frame
// and should be handled serverside anyway.
// flCurrentZoom is actually the "target" zoom we want to reach.
// flOldZoom is the zoom we started at, at the time of the change.
// flZoomLerp is how far along we are from oldZoom to currentZoom.
// flZoomLevel is the actual zoom we are at this very moment.
if (pl.flCurrentZoom != pl.flTargetZoom ) {
pl.flOldZoom = pl.flCurrentZoom;
pl.flCurrentZoom = pl.flTargetZoom ;
pl.flZoomLerp = 0.0f;
}
if (pl.flZoomLerp < 1.0f) {
// Slow this down for debugging purposes, this is meant to be fast.
// 0.8 can be safe.
// The Wastes default was 4.
pl.flZoomLerp += clframetime * 8;
if(pl.flZoomLerp >= 1.0){
//enforce the cap.
pl.flZoomLerp = 1.0;
}
//pl.flZoomLevel = getstatf(STAT_VIEWZOOM);
pl.flZoomLevel = Math_Lerp(pl.flOldZoom, pl.flCurrentZoom, pl.flZoomLerp);
}
// Set this, since Nuclide will read it in and apply instantly.
// So same effect without having to edit Nuclide, this pipes it over for it
// to do the setproperty thing below to apply
pl.viewzoom = pl.flZoomLevel;
////setproperty(VF_AFOV, DEFAULT_FOV * pl.flZoomLevel);
////setsensitivityscaler(pl.flZoomLevel);
// here's the way old FreeCS did it
//setproperty(VF_AFOV, cvar("fov") * pl.viewzoom);
//setsensitivityscaler(pl.viewzoom);
}
vector
View_ShakeCalculate(vector vecInputAngles)
{
player pl = (player)pSeat->m_ePlayer;
if(pl == NULL)return vecInputAngles; //forget this
// you might not want to force this every frame unless you like vomiting.
//pl.flViewShake = 2;
if (pl.flViewShake > 0.0f) {
vecInputAngles[0] += (random() * pl.flViewShake) * 3;
vecInputAngles[1] += (random() * pl.flViewShake) * 3;
vecInputAngles[2] += (random() * pl.flViewShake) * 3;
pl.flViewShake -= clframetime;
}
return vecInputAngles;
}
void
View_ShakeCreate(int iLevel)
{
player pl = (player)pSeat->m_ePlayer;
pl.flViewShake = iLevel / 128;
}
//TAGGG - NEW. Similar to Nuclide's provided "View_SetMuzzleflash", but also acts
// as though the first frame were an event by doing the same lines as a 5000-ish
// event. This is because TS weapons don't have events for the muzzle flash
// unlike HL ones, must be hardcoded to show up.
// Figuring out what muzzle flash for what weapon will come another time.
// For now using the same HL set, but TS comes with some muzzle-flash looking sprites.
// ALSO - for now, always assuming attachment #0.
// For the model event in case that changes, see Nuclide's src/client/modelevent.qc,
// Event_ProcessModel.
void
View_ShowMuzzleflash(int index)
{
// View_SetMuzzleflash
pSeat->m_eMuzzleflash.modelindex = (float)index;
// Event_ProcessModel: force it.
pSeat->m_eMuzzleflash.alpha = 1.0f;
pSeat->m_eMuzzleflash.scale = 0.25;
// attachment #0 is m_iVMBones + 0. Add for #1 to #3, I think.
// No idea if any weapons play with attachments, #0 should always
// be the end of the weapon for flashes
pSeat->m_eMuzzleflash.skin = pSeat->m_iVMBones + 0;
}

2
src/progs.src Normal file
View file

@ -0,0 +1,2 @@
#pragma sourcefile client/progs.src
#pragma sourcefile server/progs.src

4
src/server/Makefile Normal file
View file

@ -0,0 +1,4 @@
CC=fteqcc
all:
$(CC) progs.src

85
src/server/client.qc Normal file
View file

@ -0,0 +1,85 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* called every input frame */
void
Game_RunClientCommand(void)
{
Footsteps_Update();
//TAGGG
// FreeHL does it this way instead now (further down)?
// I don't think this is even significant.
// Then again clientside does its call this way too?
// In Predict_PlayerPreFrame, which is Nuclide.
// Yet this is within the gamemod? Odd.
//PMove_Run();
player pl = (player)self;
pl.Physics_Run();
}
/* custom chat packet */
void
SV_SendChat(entity sender, string msg, entity eEnt, float fType)
{
WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET);
WriteByte(MSG_MULTICAST, fType == 0 ? EV_CHAT:EV_CHAT_TEAM);
WriteByte(MSG_MULTICAST, num_for_edict(sender) - 1);
WriteByte(MSG_MULTICAST, sender.team);
WriteString(MSG_MULTICAST, msg);
if (eEnt) {
msg_entity = eEnt;
multicast([0,0,0], MULTICAST_ONE);
} else {
multicast([0,0,0], MULTICAST_ALL);
}
localcmd(sprintf("echo [SERVER] %s: %s\n", sender.netname, msg));
}
/* client cmd overrides happen here */
void
Game_ParseClientCommand(string cmd)
{
tokenize(cmd);
if (argv(1) == "timeleft") {
string msg;
string timestring;
float timeleft;
timeleft = cvar("mp_timelimit") - (time / 60);
timestring = Vox_TimeToString(timeleft);
msg = sprintf("we have %s minutes remaining", timestring);
Vox_Singlecast(self, msg);
return;
}
if (argv(0) == "say") {
SV_SendChat(self, argv(1), world, 0);
return;
} else if (argv(0) == "say_team") {
entity a;
for (a = world; (a = find(a, ::classname, "player"));) {
if (a.team == self.team) {
SV_SendChat(self, argv(1), a, 1);
}
}
return;
}
clientcommand(self, cmd);
}

168
src/server/damage.qc Normal file
View file

@ -0,0 +1,168 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//TAGGG - NOTE! Identical to base/src/server/damage.qc.
// Deviate from it if needed, if not by the end, including that and wiping
// this file will do.
/* generic function that applies damage, pain and suffering */
void
Damage_Apply(entity t, entity c, float dmg, int w, damageType_t type)
{
base_player tp = (base_player)t;
CGameRules rules = (CGameRules)g_grMode;
/* player god mode */
if (t.flags & FL_CLIENT && t.flags & FL_GODMODE)
return;
/* already dead, please avoid recursion */
if (t.health <= 0)
return;
/* only clients have armor */
if (t.flags & FL_CLIENT) {
/* skip armor */
if not (type & DMG_SKIP_ARMOR)
if (tp.armor && dmg > 0) {
float flArmor;
float flNewDamage;
flNewDamage = dmg * 0.2;
flArmor = (dmg - flNewDamage) * 0.5;
if (flArmor > tp.armor) {
flArmor = tp.armor;
flArmor *= (1/0.5);
flNewDamage = dmg - flArmor;
tp.armor = 0;
} else {
tp.armor -= flArmor;
}
dmg = flNewDamage;
}
}
dmg = rint(dmg);
t.health -= dmg;
/* the globals... */
g_dmg_eAttacker = c;
g_dmg_eTarget = t;
g_dmg_iDamage = dmg;
g_dmg_iHitBody = trace_surface_id;
g_dmg_iFlags = type;
g_dmg_iWeapon = w;
if (dmg > 0) {
t.dmg_take = dmg;
t.dmg_inflictor = c;
} else if (t.max_health && t.health > t.max_health) {
t.health = t.max_health;
}
CBaseEntity s = (CBaseEntity)t;
if (s.health <= 0) {
if (s.flags & FL_CLIENT) {
rules.PlayerDeath((player)s);
} else {
s.Death();
}
} else {
if (s.flags & FL_CLIENT) {
rules.PlayerPain((player)s);
} else {
s.Pain();
}
}
}
/* physical check of whether or not we can trace important parts of an ent */
float
Damage_CheckTrace(entity t, vector vecHitPos)
{
/* We're lazy. Who cares */
if (t.solid == SOLID_BSP) {
return (1);
}
traceline(vecHitPos, t.origin, 1, self);
if (trace_fraction == 1) {
return (1);
}
traceline(vecHitPos, t.origin + [15,15,0], 1, self);
if (trace_fraction == 1) {
return (1);
}
traceline(vecHitPos, t.origin + [-15,-15,0], 1, self);
if (trace_fraction == 1) {
return (1);
}
traceline(vecHitPos, t.origin + [-15,15,0], 1, self);
if (trace_fraction == 1) {
return (1);
}
traceline(vecHitPos, t.origin + [15,-15,0], 1, self);
if (trace_fraction == 1) {
return (1);
}
return (0);
}
/* even more pain and suffering, mostly used for explosives */
void
Damage_Radius(vector org, entity attacker, float dmg, float r, int check, int w)
{
float new_dmg;
float dist;
float diff;
vector pos;
for (entity e = world; (e = findfloat(e, ::takedamage, DAMAGE_YES));) {
pos[0] = e.absmin[0] + (0.5 * (e.absmax[0] - e.absmin[0]));
pos[1] = e.absmin[1] + (0.5 * (e.absmax[1] - e.absmin[1]));
pos[2] = e.absmin[2] + (0.5 * (e.absmax[2] - e.absmin[2]));
/* don't bother if it's not anywhere near us */
dist = vlen(org - pos);
if (dist > r)
continue;
/* can we physically hit this thing? */
if (check == TRUE)
if (Damage_CheckTrace(e, org) == FALSE)
continue;
/* calculate new damage values */
diff = (r - dist) / r;
new_dmg = rint(dmg * diff);
if (diff > 0) {
Damage_Apply(e, attacker, new_dmg, w, DMG_EXPLODE);
/* approximate, feel free to tweak */
if (e.movetype == MOVETYPE_WALK) {
makevectors(vectoangles(e.origin - org));
e.velocity += v_forward * (new_dmg * 5);
}
}
}
}

79
src/server/defs.h Normal file
View file

@ -0,0 +1,79 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "gamerules.h"
#include "items.h"
#include "player.h"
//TAGGG - TODO: some were intended for FreeCS like a c4 timer one, but some
// can probably be used by FreeTS anyway
// Server cvars
var int autocvar_mp_winlimit = 0;
var int autocvar_mp_halftime = 0;
var int autocvar_mp_startmoney = 30000; //was 800
//var float autocvar_mp_buytime = 90;
var float autocvar_mp_freezetime = 6;
var float autocvar_mp_roundtime = 5;
var float autocvar_mp_timelimit = 60;
var string autocvar_motdfile = "motd.txt";
var int autocvar_mp_friendlyfire = FALSE;
//TAGGG - are we even using all these?
var int autocvar_fcs_swapteams = FALSE; /* Swaps spawnpoints */
var int autocvar_fcs_maxmoney = 99999; //TAGG - was 16000.
//TAGGG - not used, don't know if the TS really supported denying weapon drop-ables.
// Doesn't mean that can't be implemented anyway (check this CVar, and if on while about
// to spawn a drop-able, deny the request and don't drop at all on player death)
//var int autocvar_fcs_nopickups = FALSE; /* Disable weapon items */
// Game specific fields
var int g_ts_gamestate;
var float g_ts_gametime;
// no, just have counts of players in general. Team-based stuff will check for that later.
//var int g_cs_alive_t;
//var int g_cs_alive_ct;
var int g_ts_player_alive_specialists;
var int g_ts_player_alive_mercenaries;
var int g_ts_player_alive_total;
var int g_ts_player_spectator; //do we even need this one?
var int g_ts_player_all;
void initSpawnMem(void);
//TAGGG - restored?
void Game_Spawn_ObserverCam(player pl);
// Do these all still exist and need to be prototyped here??
/*
void Game_SpectatorThink(void);
void Game_SpectatorConnect(void);
void Game_SpectatorDisconnect(void);
void Game_RunClientCommand(void);
void Game_ParseClientCommand(string cmd);
void Game_InitRules(void);
void Game_Worldspawn(void);
*/

View file

@ -0,0 +1,2 @@
class TSAmmoPack;

View file

@ -0,0 +1,237 @@
class TSAmmoPack{
// After being recently accessed by a player but not deleted,
// I enforce a cooldown time before any other player (including the same one)
// can try to pick me up again. This stops spamming inventory scans every
// single frame if a player stands over a weapon they can't pick up.
float accessibleCooldownTime;
// To avoid server clutter over time, a pickup will delete itself
float expireTime;
//myInfo
int ary_ammoTotal[AMMO_ID::LAST_ID];
void(void) TSAmmoPack;
virtual void() Think;
virtual void() Touch;
virtual void() PlayerUse;
static TSAmmoPack(player arg_player) generate;
virtual void(player arg_player) copyFrom;
virtual BOOLEAN(player arg_player) copyTo;
//static Container_STR_ptr *Event::commandList;
/*
// in-class constructor.
void(void) TSAmmoPack = {
// can do stuff here too.
};
*/
/*
// in-class method.
virtual int (int arg1, int arg2) doMath = {
return arg1 + arg2;
};
*/
};
static var int TSAmmoPack::testStaticVar = 0;
void TSAmmoPack::TSAmmoPack(void){
accessibleCooldownTime = -1;
for(int i = 0; i < AMMO_ID::LAST_ID; i++){
ary_ammoTotal[i] = 0; //safe default
}
}
// Static method called from elsewhere to create and initialize a
// weapon drop entity, given a player and what weapon to copy information from.
TSAmmoPack TSAmmoPack::generate(player arg_player){
TSAmmoPack eDrop = spawn(TSAmmoPack);
TSAmmoPack::testStaticVar++;
//printfline("AmmoPacks ever dropped: %i", testStaticVar);
//eDrop.setup(arg_player, arg_weaponID);
//TSAmmoPack eDrop = spawn(TSAmmoPack);
setorigin( eDrop, arg_player.origin + arg_player.view_ofs );
setmodel( eDrop, "models/ammopack.mdl");
eDrop.classname = "remove_me";
eDrop.owner = arg_player;
eDrop.movetype = MOVETYPE_TOSS;
eDrop.solid = SOLID_TRIGGER; //or CORPSE
//eDrop.weapon = dynaRefPRE.weaponID;
eDrop.think = TSAmmoPack::Think;
eDrop.touch = TSAmmoPack::Touch;
eDrop.nextthink = time + 0.5f;
eDrop.copyFrom(arg_player);
//uhh.. ok, sure
eDrop.health = 1;
setsize( eDrop, '-16 -16 0', '16 16 16' );
//is this v_forward sometimes bad.. ?
makevectors( arg_player.v_angle );
eDrop.velocity = arg_player.velocity + v_forward * 256;
//And lastly, assume I need to expire at some point.
eDrop.expireTime = time + autocvar_weaponstay; //autocvar_weaponstay
eDrop.PlayerUse = TSAmmoPack::PlayerUse;
return eDrop;
}//END OF generate
// Pack all the player's ammo pools into me.
void TSAmmoPack::copyFrom(player arg_player){
for(int i = 0; i < AMMO_ID::LAST_ID; i++){
this.ary_ammoTotal[i] = arg_player.ary_ammoTotal[i]; //take it all.
arg_player.ary_ammoTotal[i] = 0;
}
}//END OF copyFrom
// Give this player my ammo pools, whatever will fit.
// Returns whether any ammo transaction happened (pretty likely)
BOOLEAN TSAmmoPack::copyTo(player arg_player){
ammodata_t ammoRef;
BOOLEAN anyAmmoTaken = FALSE;
for(int i = 0; i < AMMO_ID::LAST_ID; i++){
ammoRef = *ary_ammoData[i];
//How much can I give to the player?
//this.ary_ammoTotal[i] = arg_player.ary_ammoTotal[i]; //take it all.
//arg_player.ary_ammoTotal[i] = 0;
int amountToTake;
int existingCount = arg_player.ary_ammoTotal[i];
int pickupCount = this.ary_ammoTotal[i];
if(existingCount + pickupCount <= ammoRef.iMax){
//absorb it all.
amountToTake = pickupCount;
}else{
//too much in the pickup? this is ok, take as much as we can.
amountToTake = ammoRef.iMax - existingCount;
}
if(amountToTake > 0){
anyAmmoTaken = TRUE;
}else{
}
arg_player.ary_ammoTotal[i] += amountToTake;
//arg_pickupRef.myInfo.iCount -= amountToTake;
this.ary_ammoTotal[i]-= amountToTake;
}//END OF for loop through ammo types
if(anyAmmoTaken){
sound(arg_player, CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM, 100, SOUNDFLAG_CUSTOMCLIENT );
}else{
//sound( arg_player, CHAN_ITEM, "common/wpn_select.wav", 1, ATTN_NORM, 100, SOUNDFLAG_CUSTOMCLIENT );
}
return anyAmmoTaken;
}//END OF copyTo
void TSAmmoPack::Touch( void ) {
if ( other.classname != "player" ) {
// assume it's the world.. play this sound?
//printfline("the thing I touched is %s", other.classname);
// TODO - any others we need to keep track of for making the drop?
if(other.classname == "worldspawn" || other.classname == "func_breakable"){
sound( this, CHAN_ITEM, "items/weapondrop1.wav", 1, ATTN_NORM, 100, SOUNDFLAG_CUSTOMCLIENT );
}
return;
}
//entity eOld = self;
//TSAmmoPack selfRef = (TSAmmoPack)self;
//self = other;
player otherPlayerRef = (player)other; //safe assumption now.
BOOLEAN shouldDelete;
if(time >= this.accessibleCooldownTime){
shouldDelete = copyTo(otherPlayerRef);
this.accessibleCooldownTime = time + 0.8;
}else{
shouldDelete = FALSE;
}
//self = eOld;
if(shouldDelete){
remove(this);
}
}//END OF touch method
void TSAmmoPack::PlayerUse( void ) {
}
void TSAmmoPack::Think( void ) {
//TSAmmoPack selfRef = (TSAmmoPack)self;
// oh.. this makes the weapon collidable with the original dropper
// again.
if(time >= this.expireTime){
//expire.
remove(this);
}else{
this.owner = world;
//set the next think to aim around this time then.
this.nextthink = this.expireTime + 0.1;
}
}

View file

@ -0,0 +1,36 @@
// NOTE - this was not a type in the original TS, just seemed convenient to make.
//class TSThrownProjectile;
class TSThrownProjectile : TSWorldGun{
void(void) TSThrownProjectile;
virtual void() Think;
virtual void() Touch;
virtual void() PlayerUse;
static TSThrownProjectile (player arg_player, int arg_weaponID) generate;
static TSThrownProjectile (player arg_player, weapondynamic_t dynaRefPRE) generate2;
virtual void (player arg_player, int arg_weaponID) copyFrom;
virtual void (player arg_player, weapondynamic_t tempRef) copyFrom2;
virtual void (player arg_player, int arg_weaponID) copyTo;
virtual void (BOOL becomeStuck, optional vector dir, optional entity stuckTo, BOOL touchedBreakableGlass)becomePickupable;
// Like FTE's supplied 'owner' field, but for keeping track of who threw this knife after
// the 'owner' field gets reset to still keep track of who should be blamed for damage inflicted.
entity m_owner;
float startAngularVelocityDelay;
//vector vPreviousVelocity;
float forceBecomePickupableTime;
vector queuedVelocity;
float queuedVelocityApplyTime;
float switchToNormalPickupBoundsTime;
};

View file

@ -0,0 +1,517 @@
// NOTE - this was not a type in the original TS, just seemed convenient to make.
// This is meant to be spawned by a player throwing a knife.
// It starts with its own behavior (hurt a non-owner I touch, or stick to / bounce-off whatever
// isn't a player), and switches over to pickup behavior after it stops being dangerous.
void TSThrownProjectile::TSThrownProjectile(void){
TSWorldGun::TSWorldGun();
startAngularVelocityDelay = -1;
//vPreviousVelocity = vecZero;
forceBecomePickupableTime = -1;
queuedVelocity = vecZero;
queuedVelocityApplyTime = -1;
switchToNormalPickupBoundsTime = -1;
}
TSThrownProjectile TSThrownProjectile::generate(player arg_player, int arg_weaponID){
weapondynamic_t dynaRefPRE = arg_player.ary_myWeapons[arg_weaponID];
return TSThrownProjectile::generate2(arg_player, dynaRefPRE);
}
// Static method called from elsewhere to create and initialize a
// weapon drop entity, given a player and what weapon to copy information from.
TSThrownProjectile TSThrownProjectile::generate2(player arg_player, weapondynamic_t dynaRefPRE){
TSThrownProjectile eDrop = spawn(TSThrownProjectile);
//eDrop.setup(arg_player, arg_weaponID);
//weapondynamic_t dynaRefPRE = arg_player.ary_myWeapons[arg_weaponID];
weapondata_basic_t* basicPointerPRE = (weapondata_basic_t*) ary_weaponData[dynaRefPRE.weaponID];
weapondata_basic_t basicRefPRE = *(basicPointerPRE);
weapondata_throwable_t throwableRef = *((weapondata_throwable_t*)basicPointerPRE);
makevectors( arg_player.v_angle );
eDrop.movetype = MOVETYPE_BOUNCE;
eDrop.solid = SOLID_TRIGGER; //or CORPSE
setorigin( eDrop, arg_player.origin + arg_player.view_ofs + v_forward * 16 );
setmodel( eDrop, basicRefPRE.sWorldModelPath );
//printfline("TSThrownProjectile::generate2 v_up: %.2f %.2f %.2f", v_up.x, v_up.y, v_up.z);
vector old_v_up = v_up;
//eDrop.velocity = arg_player.velocity + v_forward * 256;
// relying on the 'makevectors' call from earlier!
vector vDir = aim ( arg_player, 100000 );
//eDrop.velocity = ( arg_player.velocity + vDir * throwableRef.fThrowSpeed );
// we'll let player velocity influence the knives a little..
eDrop.velocity = ( arg_player.velocity*0.4 + vDir * throwableRef.fThrowSpeed );
//modify the vector we send a bit...
//-70 ?
//[-90, -90, 0]
rotatevectorsbyangle([-70, 0, 0]);
// or "v_dir" from later?
eDrop.angles = vectoangles( v_forward );
eDrop.angles.z = 90;
eDrop.classname = "remove_me";
// Also set m_owner
eDrop.owner = arg_player;
eDrop.m_owner = eDrop.owner;
eDrop.touch = TSThrownProjectile::Touch;
eDrop.think = TSThrownProjectile::Think;
eDrop.nextthink = time + 0.0; //every frame.
//eDrop.PlayerUse = TSWorldGun::PlayerUse;
// "use" does nothing to me... yet.
eDrop.PlayerUse = NULL;
eDrop.gravity = throwableRef.fThrowGravity;
eDrop.copyFrom2(arg_player, dynaRefPRE);
//uhh.. ok, sure
eDrop.health = 1;
// TAGGG - NOTE.
// Somewhere above 1.4? sticks a foot out of the wall (floating in space; none of the knife is in it)
// Somewhere at/below 1.4? stuck too far in the wall.
// So we'll need a traceline to get right against the thing we collided with?
// AND still, throwing at glass doesn't work quite perfect either way.
// With undersized bounds, it registers as a hit on the glass but is also stopped
// by the glass, like grenades are...
// with oversized bounds, the game thinks we hit the map first anyway (reported entity hit:
// 'worldpspawn') even though destroying the glass separately and throwing again has no
// collision at all in that space, leading me to believe it collided with the glass but called
// it worldspawn anyway.
// W. H. A. T.
//TAGGG - TODO. then. apparently.
//setsize( eKnife, '-1.7 -1.7 -1.7', '1.7 1.7 1.7' );
//setsize( eKnife, '-1.4 -1.4 -1.4', '1.4 1.4 1.4' );
//setsize( eKnife, '-1 -1 -1', '1 1 1' );
setsize( eDrop, '-1 -1 0', '1 1 1' );
//setsize( eDrop, '-16 -16 0', '16 16 16' );
//And lastly, assume I need to expire at some point.
// Expires 10 seconds after becoming a pickup. But just in case it never
// does for some odd reason, no business existing too long anyway.
eDrop.expireTime = time + 30;
//eDrop.vPreviousVelocity = eDrop.velocity;
eDrop.startAngularVelocityDelay = time + 0.008; //0.013?
eDrop.bouncefactor = 0.2;
eDrop.friction = 0.78;
return eDrop;
}//END OF generate
void TSThrownProjectile::becomePickupable(BOOL becomeStuck, optional vector dir, optional entity stuckTo, optional BOOL touchedBreakableGlass){
forceBecomePickupableTime = -1;
//in a short while, switch to normal bounds.
if(switchToNormalPickupBoundsTime == -1){
switchToNormalPickupBoundsTime = time + 0.7;
}
if(becomeStuck){
/*
makevectors( normalize(this.velocity) );
//rotatevectorsbyangle([-70, 0, 0]);
// or "v_dir" from later?
this.angles = vectoangles(v_forward);
*/
if ( touchedBreakableGlass ) {
//no! Go through it.
// this means, insta-break the breakable glass we hit.
Damage_Apply( other, this.m_owner, other.health, myInfo.weaponID, DMG_SLASH);
queuedVelocity = this.velocity;
queuedVelocityApplyTime = time + 0.01;
return; //don't do the rest of this method.
}else{
//stuck as planned.
this.angles = vectoangles(normalize(this.velocity));
this.angles.z = 90;
this.velocity = [0,0,0];
startAngularVelocityDelay = -1; //if we haven't reached this, certianly don't try to!
// assume we can trust trace_endpos.
this.origin = trace_endpos - dir * 5.9;
//this.origin -= dir * 5.9;
this.movetype = MOVETYPE_FLY;
}
}else{
//safe assumption?
//this.angles.x = 0;
this.angles.z = 0;
this.gravity = 0.9;
}
// don't make the weapon drop noises. that is all.
//this.touch = TSWorldGun::Touch;
this.touch = NULL;
this.PlayerUse = TSWorldGun::PlayerUse;
// not yet freeman!
//this.think = TSWorldGun::Think;
//this.nextthink = time + 0.2f;
//this.expireTime = time + autocvar_weaponstay;
// for some reason knives always expire in X seconds.
// probably ok in case a bunch of players throw knives in a short timespan,
// don't want to spam it up.
this.expireTime = time + 30;
this.avelocity = [0,0,0];
}//END OF becomePickupable
void TSThrownProjectile::copyFrom(player arg_player, int arg_weaponID){
weapondynamic_t tempRef = arg_player.ary_myWeapons[arg_weaponID];
copyFrom2(arg_player, tempRef);
}
// From what index in the player's inventory should I copy stats from?
// Called as we're being filled to be thrown from the player.
void TSThrownProjectile::copyFrom2(player arg_player, weapondynamic_t tempRef){
//weapondynamic_t tempRef = arg_player.ary_myWeapons[arg_weaponID];
COPYVARFROM(weaponID)
COPYVARFROM(weaponTypeID)
//COPYVARFROM(iBitsUpgrade)
//actually want to exclude the AKIMBO and FULLLOAD bits. They were only for the shop really.
myInfo.iBitsUpgrade = tempRef.iBitsUpgrade & ~(BITS_WEAPONOPT_AKIMBO | BITS_WEAPONOPT_FULLLOAD);
//COPYVARFROM(iCount)
weapondata_basic_t* weaponPointer = ary_weaponData[tempRef.weaponID];
weapondata_basic_t basicRef = *((weapondata_basic_t*) weaponPointer);
// only one throwable is 'thrown' at a time ever.
myInfo.iCount = 1;
COPYVARFROM(iPrice) //who cares.
//COPYVARFROM(iSlots)
//re-calculate the slots now.
myInfo.iSlots = myInfo.iCount * basicRef.iSlots;
//doesn't use these. We're a throwable.
myInfo.iClipLeft = 0;
myInfo.iClipAkimboLeft = 0;
COPYVARFROM(iBitsUpgrade_on)
COPYVARFROM(iFireMode)
COPYVARFROM(iFireModeAkimbo)
//COPYVARFROM(iIronSight)
COPYVARFROM(forceBodygroup1Submodel)
myInfo.iIronSight = 0; //default: not using it.
}//END OF copyFrom
// From what index in the player's inventory should I copy stats from?
// Called as we're being picked up by a player to fill one of their weapon slots.
void TSThrownProjectile::copyTo(player arg_player, int arg_weaponID){
weapondynamic_t tempRef = arg_player.ary_myWeapons[arg_weaponID];
COPYVARTO(weaponID)
COPYVARTO(weaponTypeID)
//COPYVARTO(iBitsUpgrade)
//COPYVARTO(iCount)
COPYVARTO(iPrice) //no one cares.
//COPYVARTO(iSlots) //re-calculated.
//COPYVARTO(iClipLeft)
//COPYVARTO(iClipAkimboLeft)
//COPYVARTO(iBitsUpgrade_on)
//COPYVARTO(iFireMode)
//COPYVARTO(iFireModeAkimbo)
//COPYVARTO(iIronSight)
COPYVARTO(forceBodygroup1Submodel)
tempRef.iIronSight = 0; //default: not using it.
}//END OF copyTo
void TSThrownProjectile::Touch( void ) {
if ( other.solid == SOLID_TRIGGER ) {
return;
}
if ( other == this.owner ) {
return;
}
// func_water has "other.solid == SOLID_BSP" but is still pass-thru-able??
// WTF DO I DO.
// TAGGG - CRITICAL. - when done, get new findings over to the grenade too, other throwable.
if(other.solid == SOLID_NOT || other.solid == SOLID_TRIGGER || other.classname == "func_water"){
//ok? don't react.
return;
}
vector dir = normalize(this.velocity);
// was 15 each??
//HACKY TEST
//printfline("knife vel %.2f %.2f %.2f", velocity.x, velocity.y, velocity.z);
vector src = this.origin - this.velocity*0.06;
vector end = this.origin + this.velocity*0.06;
traceline(src, end, MOVE_HITMODEL, this);
//printfline("TRACEFRACTO %.2f", trace_fraction);
if(trace_fraction == 1){
//WTF??? all we can do.
trace_endpos = this.origin;
}
//TAGGG - TODO. Use the trace from further below's point of intersection instead of
// "this.origin", mabey this will help..?
//string hitTexture = getsurfacetexture(other, getsurfacenearpoint(other, this.origin));
string hitTexture = getsurfacetexture(other, getsurfacenearpoint(other, trace_endpos));
printfline("TSThrownProjectile::Touch I TOUCHED %s solid:%d neartex:%s", other.classname, other.solid, hitTexture);
if(TRUE){
//stop on anything else.
// TODO - delete this on hitting a player other than the owner (not that this should even cause
// a touch event then) and just stay stuck to any other thing, like map geometry.
BOOL touchedBreakableGlass = (( other.classname == "func_breakable" ) && ( other.material == GSMATERIAL_GLASS ));
if(other == world || other.classname == "func_wall" || touchedBreakableGlass){
//okie, get stuck.
//TAGGG - TODO. Spark effect!
/* FIXME: more universal check? */
//...yea there are obvious cases where this fails.
//TAGGG - CRITICAL. HOW DO I FIX THIS. knivees can just get stuck on the skybox
// still from the nearest texture not being 'sky'.
if (hitTexture == "sky") {
//printfline("Knife: I DONE WENT OUT OF BOUNDS");
remove(this);
return;
}
//notice, ATTN_NORM. Sound goes further.,
sound( this, CHAN_WEAPON, "weapons/knife/knife_hit.wav", 1, ATTN_NORM - 0.15 );
//printfline("trace_fraction %.2f", trace_fraction);
// Do we get stuck or bounce off?
if(trace_fraction != 1.0){
float theProd = dotproduct(dir, trace_plane_normal);
//printfline("WHAT THE hek IS THIS %.2f", theProd);
if(theProd <= -0.73){
//close to being opposites? be stuck
becomePickupable(TRUE, dir, other, touchedBreakableGlass);
}else{
//Did we hit the ground?
float dotIsGround = dotproduct(trace_plane_normal, '0 0 1');
//printfline("AM I GROUND %.2f", dotIsGround);
if(dotIsGround >= 0.91){
//printfline("IM A proud TREE dandy FROG %.2f", this.velocity.z);
if(this.velocity.z <= -260){
//Going downards much? Get stuck in the ground.
// Hack to look like it's more downwards into the ground.
this.velocity.x *= 0.18;
this.velocity.y *= 0.18;
becomePickupable(TRUE, dir, other, touchedBreakableGlass);
}else{
//Bounce off the ground instead.
becomePickupable(FALSE);
//setsize( this, '-16 -16 0', '16 16 16' );
this.angles.x = 0; //go flat as you fall off.
this.velocity = [this.velocity.x * 0.09, this.velocity.y * 0.09, this.velocity.z];
this.movetype = MOVETYPE_BOUNCE;
}
//TODO - what is "bouncestop" ??
//printfline("GIMMIE DA STATS %.2f %.2f %.2f", mass, bouncestop, bouncefactor);
}else{
this.movetype = MOVETYPE_BOUNCE;
//no? just keep going, probably.
forceBecomePickupableTime = time + 1;
// thank you w_gauss.c!
float n = -dotproduct(trace_plane_normal, dir);
vector reflectedVel = vlen(this.velocity) * (2 * trace_plane_normal * n + dir);
// yes. we have to do this. Thanks, engine.
this.origin += trace_plane_normal * 2;
setorigin(this, this.origin);
queuedVelocity = [reflectedVel.x * 0.8, reflectedVel.y * 0.8, reflectedVel.z];
if(queuedVelocity.z > 0){
//positive? reduce it a bit.
queuedVelocity.z *= 0.85;
}
queuedVelocityApplyTime = time + 0.01;
// hold up! Angles?
vector daAngah = vectoangles(normalize(queuedVelocity));
int oldPitch = angles.x;
this.angles = daAngah;
this.angles.x = oldPitch;
this.angles.z = 90;
}
}
}else{
//??? just assume sticking out this much is ok.
// assume stuck.
becomePickupable(TRUE, dir, other, touchedBreakableGlass);
}
//setsize( this, '-16 -16 0', '16 16 16' );
}else{
printfline("TSThrownProjectile::Touch HIT ENTITY? WHATS IT CALLED %s TAKEDAMAGE? %d", other.classname, other.takedamage);
//is it something we can deal damage to?
if(other.takedamage == DAMAGE_YES){
//is it fleshy? eh, assume it is.
//there is always a "trace_ent.iBleeds" check though.
sound( this, CHAN_WEAPON, "weapons/knife/knife_hitbody.wav", 1, ATTN_IDLE );
weapondata_basic_t* basicPointerPRE = (weapondata_basic_t*) ary_weaponData[this.myInfo.weaponID];
//weapondata_basic_t basicRefPRE = *(basicPointerPRE);
weapondata_throwable_t throwableRef = *((weapondata_throwable_t*)basicPointerPRE);
// tell the thing being damaged that my OWNER was what damaged it.
Damage_Apply(other, this.m_owner, throwableRef.fThrowDamage, myInfo.weaponID, DMG_SLASH);
remove(this);
return;
}else{
//oh. bounce off I guess.
//TAGGG - TODO. Spark effect!
//TODO - bounce the opposite direction floor-wise?
sound( this, CHAN_WEAPON, "weapons/knife/knife_hit.wav", 1, ATTN_NORM - 0.15 );
becomePickupable(FALSE);
//setsize( this, '-16 -16 0', '16 16 16' );
this.angles.x = 0; //go flat as you fall off.
this.velocity = [this.velocity.x * 0.24, this.velocity.y * 0.24, this.velocity.z];
this.movetype = MOVETYPE_BOUNCE;
}
}
//this.touch = NULL; //no more responses.
}
}//END OF touch method
void TSThrownProjectile::PlayerUse( void ) {
//EMPTY. Use TSWorldGun's PlayerUse once we dropped,
// OR have logic that redirects to that in here when we want to make this pickup-able.
// Whichever works.
}
void TSThrownProjectile::Think( void ) {
if(this.startAngularVelocityDelay != -1 && time >= this.startAngularVelocityDelay){
this.startAngularVelocityDelay = -1;
this.avelocity = [360 * 2.7, 0, 0];
}
if(forceBecomePickupableTime != -1 && time >= forceBecomePickupableTime){
//check. are we moving too slow? keep rechecking after.
forceBecomePickupableTime = time + 1;
if(vlen(velocity) < 8){
//ok.
forceBecomePickupableTime = -1;
becomePickupable(FALSE);
//setsize( this, '-16 -16 0', '16 16 16' );
this.angles.x = 0; //go flat as you fall off.
this.velocity = [this.velocity.x * 0.07, this.velocity.y * 0.07, this.velocity.z];
this.movetype = MOVETYPE_BOUNCE;
}
}
if(queuedVelocityApplyTime != -1 && time >= queuedVelocityApplyTime){
queuedVelocityApplyTime = -1;
this.velocity = queuedVelocity;
}
if(time >= expireTime){
// that's the end.
remove(self);
return;
}
if(switchToNormalPickupBoundsTime >= 0 && time >= switchToNormalPickupBoundsTime){
switchToNormalPickupBoundsTime = -2; //to not try this again.
// and resume the normal think method.
setsize( this, '-16 -16 0', '16 16 16' );
this.think = TSWorldGun::Think;
this.nextthink = time + 0.01f;
//printfline("OH YEAHHHHHHHHhhhhh");
return;
}
//this.vPreviousVelocity = this.velocity;
this.nextthink = time + 0.0;
}

View file

@ -0,0 +1,36 @@
//class TSWorldGun;
// not inheriting from CBaseEntity... this wasn't made with that in mind,
// and it doesn't look like it needs anything from there.
// If there were a version of this to be respawnable, it may make sense to make it a child of
// CBaseEntiy and then remove the GF_CANRESPAWN flag gflags added to all CBaseEntities by default
// (I think)... since changed to the IDENTITY_CANRESPAWN flag of ".identity" for CBaseEntity's
class TSWorldGun{
// After being recently accessed by a player but not deleted,
// I enforce a cooldown time before any other player (including the same one)
// can try to pick me up again. This stops spamming inventory scans every
// single frame if a player stands over a weapon they can't pick up.
float accessibleCooldownTime;
// To avoid server clutter over time, a pickup will delete itself
float expireTime;
weapondynamic_t myInfo;
void(void) TSWorldGun;
virtual void() Think;
virtual void() Touch;
virtual void() PlayerUse;
static TSWorldGun (player arg_player, int arg_weaponID) generate;
static TSWorldGun (player arg_player, weapondynamic_t dynaRefPRE) generate2;
virtual void (player arg_player, int arg_weaponID) copyFrom;
virtual void (player arg_player, weapondynamic_t tempRef) copyFrom2;
virtual void (player arg_player, int arg_weaponID) copyTo;
};

View file

@ -0,0 +1,259 @@
void TSWorldGun::TSWorldGun(void){
accessibleCooldownTime = -1;
myInfo = spawn(weapondynamic_t);
}
TSWorldGun TSWorldGun::generate(player arg_player, int arg_weaponID){
weapondynamic_t dynaRefPRE = arg_player.ary_myWeapons[arg_weaponID];
return TSWorldGun::generate2(arg_player, dynaRefPRE);
}
// Static method called from elsewhere to create and initialize a
// weapon drop entity, given a player and what weapon to copy information from.
TSWorldGun TSWorldGun::generate2(player arg_player, weapondynamic_t dynaRefPRE){
TSWorldGun eDrop = spawn(TSWorldGun);
//eDrop.setup(arg_player, arg_weaponID);
//weapondynamic_t dynaRefPRE = arg_player.ary_myWeapons[arg_weaponID];
weapondata_basic_t* basicPointerPRE = (weapondata_basic_t*) ary_weaponData[dynaRefPRE.weaponID];
weapondata_basic_t basicRefPRE = *(basicPointerPRE);
eDrop.movetype = MOVETYPE_TOSS;
eDrop.solid = SOLID_TRIGGER; //or CORPSE
//TSWorldGun eDrop = spawn(TSWorldGun);
setorigin( eDrop, arg_player.origin + arg_player.view_ofs );
setmodel( eDrop, basicRefPRE.sWorldModelPath );
eDrop.angles = [0, randomInRange_f(0, 360), 0];
eDrop.avelocity = [0, randomInRange_f(100, 500), 0];
eDrop.classname = "remove_me";
eDrop.owner = arg_player;
//eDrop.weapon = dynaRefPRE.weaponID;
eDrop.think = TSWorldGun::Think;
eDrop.touch = TSWorldGun::Touch;
eDrop.nextthink = time + 0.5f;
eDrop.copyFrom2(arg_player, dynaRefPRE);
//uhh.. ok, sure
eDrop.health = 1;
setsize( eDrop, '-16 -16 0', '16 16 16' );
//is this v_forward sometimes bad.. ?
makevectors( arg_player.v_angle );
eDrop.velocity = arg_player.velocity + v_forward * 320; //256?
eDrop.gravity = 0.9;
//And lastly, assume I need to expire at some point.
eDrop.expireTime = time + autocvar_weaponstay; //autocvar_weaponstay
eDrop.PlayerUse = TSWorldGun::PlayerUse;
return eDrop;
}//END OF generate
void TSWorldGun::copyFrom(player arg_player, int arg_weaponID){
weapondynamic_t tempRef = arg_player.ary_myWeapons[arg_weaponID];
copyFrom2(arg_player, tempRef);
}
// From what index in the player's inventory should I copy stats from?
// Called as we're being filled to be thrown from the player.
void TSWorldGun::copyFrom2(player arg_player, weapondynamic_t tempRef){
//weapondynamic_t tempRef = arg_player.ary_myWeapons[arg_weaponID];
COPYVARFROM(weaponID)
COPYVARFROM(weaponTypeID)
//COPYVARFROM(iBitsUpgrade)
//actually want to exclude the AKIMBO and FULLLOAD bits. They were only for the shop really.
myInfo.iBitsUpgrade = tempRef.iBitsUpgrade & ~(BITS_WEAPONOPT_AKIMBO | BITS_WEAPONOPT_FULLLOAD);
//COPYVARFROM(iCount)
weapondata_basic_t* weaponPointer = ary_weaponData[tempRef.weaponID];
weapondata_basic_t basicRef = *((weapondata_basic_t*) weaponPointer);
if(basicRef.typeID == WEAPONDATA_TYPEID_THROWABLE){
//we're a throwable? Send the count over.
COPYVARFROM(iCount)
}else{
// Always a count of 1. Any dropped weapon from akimbo is really just "one" of the two pickups needed.
// Even weapons that only come in akimbo, like goldencolts, don't hurt by being called a count of "1".
// They keep the same shop stats, which is really the point here.
myInfo.iCount = 1;
}
COPYVARFROM(iPrice) //who cares.
//COPYVARFROM(iSlots)
//re-calculate the slots now.
myInfo.iSlots = myInfo.iCount * basicRef.iSlots;
//Hold on... this isn't so simple.
if(basicRef.typeID == WEAPONDATA_TYPEID_GUN || basicRef.typeID == WEAPONDATA_TYPEID_IRONSIGHT){
if(basicRef.iBitsUpgradeAuto & BITS_WEAPONOPT_AKIMBO){
//If the weapon we're spawned from is forced akimbo, the intuitive way works.
//iClipLeft & iClipAkimboLeft can be filled, this is a singular drop to represent akimbo.
COPYVARFROM(iClipLeft)
COPYVARFROM(iClipAkimboLeft)
}else if(basicRef.iBitsUpgrade & BITS_WEAPONOPT_AKIMBO && tempRef.iBitsUpgrade & BITS_WEAPONOPT_AKIMBO){
//If the weapon supports akimbo and we have it, assume the thing dropped is the 2nd weapon.
//That is, the one tied to "iClipAkimboLeft".
if(arg_player.weaponEquippedAkimbo){
myInfo.iClipLeft = tempRef.iClipAkimboLeft;
myInfo.iClipAkimboLeft = 0;
}else{
//oh.. drop the singular varient's ammo count then.
myInfo.iClipLeft = tempRef.iClipLeft;
myInfo.iClipAkimboLeft = 0;
}
}else{
//not akimbo or doesn't support it? Only this one.
myInfo.iClipLeft = tempRef.iClipLeft;
myInfo.iClipAkimboLeft = 0;
}
}else{
//doesn't use these.
myInfo.iClipLeft = 0;
myInfo.iClipAkimboLeft = 0;
}
COPYVARFROM(iBitsUpgrade_on)
COPYVARFROM(iFireMode) //we can always copy both firemodes to any individual weapon, can't really hurt.
COPYVARFROM(iFireModeAkimbo)
//COPYVARFROM(iIronSight)
COPYVARFROM(forceBodygroup1Submodel)
myInfo.iIronSight = 0; //default: not using it.
}//END OF copyFrom
// From what index in the player's inventory should I copy stats from?
// Called as we're being picked up by a player to fill one of their weapon slots.
void TSWorldGun::copyTo(player arg_player, int arg_weaponID){
weapondynamic_t tempRef = arg_player.ary_myWeapons[arg_weaponID];
COPYVARTO(weaponID)
COPYVARTO(weaponTypeID)
//COPYVARTO(iBitsUpgrade)
//COPYVARTO(iCount)
COPYVARTO(iPrice) //no one cares.
//COPYVARTO(iSlots) //re-calculated.
//COPYVARTO(iClipLeft)
//COPYVARTO(iClipAkimboLeft)
//COPYVARTO(iBitsUpgrade_on)
//COPYVARTO(iFireMode)
//COPYVARTO(iFireModeAkimbo)
//COPYVARTO(iIronSight)
COPYVARTO(forceBodygroup1Submodel)
tempRef.iIronSight = 0; //default: not using it.
}//END OF copyTo
void TSWorldGun::Touch( void ) {
if ( other.classname != "player" ) {
// assume it's the world.. play this sound?
//printfline("the thing I touched is %s", other.classname);
// TODO - any others we need to keep track of for making the drop?
if(other.classname == "worldspawn" || other.classname == "func_breakable"){
sound( this, CHAN_ITEM, "items/weapondrop1.wav", 1, ATTN_NORM, 100, SOUNDFLAG_CUSTOMCLIENT );
}
return;
}
}//END OF touch method
void TSWorldGun::PlayerUse( void ) {
//laziness.
//entity other = eActivator;
//...no, hides a global, not wise.
other = eActivator; //this however should be fine?
// !!! IMPORTANT !!!
// Want to be able to pick me up rapidly? You have to remove this check too,
// or setting the owner to "player" on generation.
// This means the player that spawend me can't pick me up for a little while.
// This is completely independent of accessibleCooldownTime, which prevents
// all players from picking me up too soon instead.
if ( other.classname != "player" ) {
return;
} else if ( other == this.owner ) {
return;
}
//entity eOld = self;
//TSWorldGun selfRef = (TSWorldGun)self;
//self = other;
player otherPlayerRef = (player)other; //safe assumption now.
BOOLEAN shouldDelete;
if(time >= this.accessibleCooldownTime){
shouldDelete = otherPlayerRef.attemptAddWeaponFromPickup(this);
this.accessibleCooldownTime = time + 0.8;
}else{
shouldDelete = FALSE;
}
//self = eOld;
if(shouldDelete){
remove(this);
}
}
void TSWorldGun::Think( void ) {
//TSWorldGun selfRef = (TSWorldGun)self;
// oh.. this makes the weapon collidable with the original dropper
// again.
if(time >= this.expireTime){
//expire.
remove(this);
}else{
this.owner = world;
//set the next think to aim around this time then.
this.nextthink = this.expireTime + 0.1;
}
}

View file

View file

View file

View file

View file

View file

View file

View file

@ -0,0 +1,11 @@
class ts_powerup;
#ifdef CSQC
// static method... essentially at least.
void Powerup_Parse(void);
#endif

View file

@ -0,0 +1,725 @@
#define POWERUP_MODEL_PATH "models/powerup.mdl"
enum pwuptype_choice{
Random = 0,
Slow_Motion = 1,
Infinite_Clip = 2,
Kung_Fu = 4,
Slow_Pause = 8,
Double_Firerate = 16,
Grenade = 32,
Health = 64,
Armor = 128,
Low_Gravity = 256 //better known as super/power jump? ehh see the manual, it has the boots icon'd one.
};
#ifdef SSQC
class ts_powerup:CBaseEntity{
//key values?
pwuptype_choice m_ePwuptype;
int m_iPwupduration;
string m_strMessage;
float expireTime;
float respawnTime;
// actual picked submodel. Can be looked at for logic on touch.
// nahhh, just take it from the powerup_data_... structs.
//int submodelIndex;
// What member of ary_powerupData do I get my behavior/model/etc. from?
POWERUP_ID dataID;
BOOL alreadyPickedUp;
BOOL isRespawnCall;
vector mem_origin;
void(void) ts_powerup;
virtual void(void) CustomInit;
//virtual void(void) restartAnim;
virtual float(entity pvsent, float cflags) send;
virtual void(void) Respawn;
virtual void(void) Think;
virtual void(void) Touch;
virtual void(void) determineDataID;
virtual void(void) declareDisposable;
};
void ts_powerup::ts_powerup(void){
// And why don't we just receive these as name/value pairs like clientside does?
// I HAVE NO CLUE.
// And is it a problem we never call CBaseEntity's constructor?
// If we did it would read every single field that way, in addition to us
// reading every single field here, just to get info that the CBaseEntity expects
// (origin) and what ts_powerup expects. Not efficient!
int nfields = tokenize( __fullspawndata );
for (int i = 1; i < (nfields - 1 ); i += 2) {
switch ( argv( i ) ) {
case "pwuptype":
m_ePwuptype = stoi(argv(i + 1));
break;
// this is actually the time that passes before respawning.
// So at one time this was meant to determine how long a powerup lasts once used? really?
case "pwupduration":
m_iPwupduration = stoi(argv(i + 1));
break;
// Pretty sure this goes completely unused. Wouldn't only some tutorial have any reason
// to put a message on screen upon touching a powerup (so I assume is the point)?
case "message":
m_strMessage = argv(i + 1);
break;
//VECTOR PARAMETER EXAMPLE (not in this one though):
/*
case "color":
colormod = stov( argv( i + 1 ) );
break;
*/
default:
break;
}
}
//gflags |= GF_CANRESPAWN;
}//END OF constructor
void ts_powerup::CustomInit(void){
expireTime = -1; //assumption.
respawnTime = -1;
//anything spechul?
//setmodel( this, "models/powerup.mdl" );
this.isRespawnCall = TRUE;
this.alreadyPickedUp = FALSE;
this.classname = "ts_powerup";
this.owner = world;
this.movetype = MOVETYPE_TOSS;
this.solid = SOLID_TRIGGER; //or CORPSE
this.pvsflags = PVSF_NOREMOVE | PVSF_IGNOREPVS;
//customphysics?
//eDrop.think = ts_powerup::Think;
//eDrop.touch = ts_powerup::Touch;
//this.nextthink = time + 0.5f;
//uhh.. ok, sure
//this.health = 1;
setsize( this, '-24 -24 0', '24 24 48' );
//this.modelflags = ?
this.frame = 0;
this.frame1time = 0;
//frames: 31
//FPS: 20
//this.think = restartAnim;
//this.nextthink = time + (31-2)/20;
this.model = POWERUP_MODEL_PATH;
//takedamage = DAMAGE_NO;
// start at where I spawned, already recorded by CBaseEntity for us.
origin = m_oldOrigin;
// Tone the gravity down to see them fall better / prove their positions are reset on spawn.
//this.gravity = 0.006;
// And this lets the powerup start against the ground, compared to where it was placed on the map.
// Original TS might not even do this, but hey. Why not, it would end up here anyway in a second of
// gametime.
if(!droptofloor()){
vector vSource = this.origin;
//we must take matters into our own hands.
traceline ( vSource, vSource + ( '0 0 -1' * 300 ), TRUE, this );
//entity ef = findradius(trace_endpos, 40);
this.origin[2] = trace_endpos[2];
}
// forget if we were on the ground.
// Yes, we have to do this or we forget to fall since a origin force-change (teleport)
// I LOVE YOU, ENGINE.
flags &= ~FL_ONGROUND;
groundentity = NULL;
// Do this AFTER your position-shifting shenanigans you hooligan!
setorigin(this, origin);
determineDataID();
// not necessarily the origin at spawntime,
// this keeps track of the origin since the last time we let the client know what it is.
// Yes. Really.
mem_origin = this.origin;
this.SendEntity = send;
this.SendFlags = 1;
this.touch = ts_powerup::Touch;
think = ts_powerup::Think;
this.nextthink = time + 0;
// ??? SERVERTEST
//setmodel( this, "models/powerup.mdl" );
//m_oldModel = POWERUP_MODEL_PATH;
//RendermodeUpdate();
}//END OF CustomInit
float ts_powerup::send(entity pvsent, float cflags)
{
WriteByte(MSG_ENTITY, ENT_POWERUP);
WriteCoord(MSG_ENTITY, this.origin[0]);
WriteCoord(MSG_ENTITY, this.origin[1]);
WriteCoord(MSG_ENTITY, this.origin[2]);
WriteCoord(MSG_ENTITY, this.angles[0]);
WriteCoord(MSG_ENTITY, this.angles[1]);
WriteCoord(MSG_ENTITY, this.angles[2]);
WriteByte(MSG_ENTITY, this.dataID);
WriteByte(MSG_ENTITY, this.alreadyPickedUp);
WriteByte(MSG_ENTITY, this.isRespawnCall);
this.isRespawnCall = FALSE; //only send once this time.
//WriteByte(MSG_ENTITY, dataID);
//WriteString(MSG_ENTITY, this.texture);
return TRUE;
}
void ts_powerup::Respawn(void){
CustomInit();
}//END OF Respawn
void ts_powerup::Think(void){
//TAGGG - CRITICAL. DISCUSS.
// Is it ok to resend the entity to the client(s) just because our position changed?
// There is no way around this? Any part of physics handled clientside for an ounce of prediction? etc.?
// Would falling look odd on low internet connections?
// Yes pre-placed map powerups wouldn't spend much time falling. Or any if we're using the 'detect & snap to ground'
// feature, but this is more of a concern for other things that seem to lose all default functionality for server-client
// interactions (setting SendEntity ever I think is what causes this?)
if(respawnTime != -1 && time >= respawnTime){
// re-appear then!
respawnTime = -1;
Respawn();
return;
}
if(this.origin != mem_origin){
mem_origin = this.origin;
//uh-oh. we gotta tell the client.
this.SendEntity = send;
this.SendFlags = 1;
}
this.nextthink = time + 0.01;
}//END OF think
void ts_powerup::Touch(void){
if(other.classname != "player"){
return;
}
//TAGGG - TODO. Actually query to see whether our type of powerup is usable, and, if so, forbid
// adding to the player if they already have a usable powerup equipped (stored for use).
// Any other time, this powerup always gets deleted & makes the gunpickup noise in original TS behavior.
// Deviate however, or not.
sound( other, CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM, 100, SOUNDFLAG_CUSTOMCLIENT );
//remove(self);
if(this.expireTime == -1){
//pre-placed by the map? Create the illusion we disappeared.
CBaseEntity::Hide();
this.alreadyPickedUp = TRUE;
//respawn 'm_iPwupduration seconds from now, often 60 (at least it is in ts_bikini)
this.respawnTime = time + m_iPwupduration;
this.SendEntity = send;
this.SendFlags = 1;
}else{
//We were going to expire at some point? Do so now then, no need to hide to never be respawned and deleted later.
remove(self);
}
}//END OF Touch
void ts_powerup::determineDataID(void){
int bitsToChoose[9];
// really.. this is just setting bitsToChoose[0] to 0. Stops a 'uninitialized variable'
// warning when done this way instead.
// God I love FTE.
(*bitsToChoose) = 0;
//TEST to see how this works out.
// Should only ever spawn karate and double-fire.
//m_ePwuptype = 4 | 16;
// And infinite clip/double clip, and slow pause/old slow pause.
//m_ePwuptype = 2 | 8;
int _1Count = 0;
int currentBit = 1;
int bitmask = m_ePwuptype;
int bitEnd = 256;
while(TRUE){
if(bitmask & currentBit){
bitsToChoose[_1Count] = currentBit;
_1Count += 1;
}
if(currentBit < bitEnd){
currentBit = currentBit << 1; //next.
}else{
//stop!
break;
}
}
int bitIndexChosen = 0;
int bitChosen = 0;
if(_1Count == 0){
//choose from them all now.
bitIndexChosen = randomInRange_i(0, 8);
bitChosen = (1 << bitIndexChosen);
}else if(_1Count == 1){
bitIndexChosen = 0;
bitChosen = bitsToChoose[bitIndexChosen];
}else{
bitIndexChosen = randomInRange_i(0, _1Count-1);
bitChosen = bitsToChoose[bitIndexChosen];
}
//submodel 4: a pause sign. ???
//submodel 11: double-magazine (possible replacement for infinite ammo in some versions)?
if(bitChosen == 1){
//slow motion
//submodelIndex = 5;
dataID = POWERUP_ID::SlowMotion;
}else if(bitChosen == 2){
//"Infinite Clip"
//submodelIndex = 3;
if(_1Count == 1){
//force the original only.
dataID = POWERUP_ID::InfiniteClip;
}else{
//I was picked of at least 1 other choice? Allow me to be the alternate too.
if(random() < 0.5){
dataID = POWERUP_ID::InfiniteClip;
}else{
dataID = POWERUP_ID::DoubleClip;
}
}
}else if(bitChosen == 4){
//"Kung Fu"
//submodelIndex = 1;
dataID = POWERUP_ID::KungFu;
}else if(bitChosen == 8){
//Slow Pause
//submodelIndex = 10;
if(_1Count == 1){
//force the original only.
dataID = POWERUP_ID::SlowPause;
}else{
//I was picked of at least 1 other choice? Allow me to be the alternate too.
if(random() < 0.5){
dataID = POWERUP_ID::SlowPause;
}else{
dataID = POWERUP_ID::SlowPauseOld;
}
}
}else if(bitChosen == 16){
// Double Firerate
//submodelIndex = 2;
dataID = POWERUP_ID::DoubleFirerate;
}else if(bitChosen == 32){
//Grenade
//submodelIndex = 6;
dataID = POWERUP_ID::Grenade;
}else if(bitChosen == 64){
//Health
//submodelIndex = 7;
dataID = POWERUP_ID::Health;
}else if(bitChosen == 128){
//Armor
//submodelIndex = 8;
dataID = POWERUP_ID::Armor;
}else if(bitChosen == 256){
//Low gravity (or rather super jump)
//submodelIndex = 9;
dataID = POWERUP_ID::SuperJump;
}
}//END OF determineDataID
void ts_powerup::declareDisposable(void){
// Since we clearly weren't spawned by the map for this method to be called,
// we need to do what "Respawn" would. The would-be constructor stuff.
CustomInit();
this.classname = "remove_me";
// TODO - decrease alpha slowly until the epiretime is up?
// It's really bizarre original TS did that, but hey. We don't have to replicate every little detail,
// or if we greatly prefer whatever working a different way.
// ALSO - the # seconds is an assumtpion, original powerup expire time not known yet.
this.expireTime = time + 30;
// actually not needed. Things with the "remove_me" classname are deleted,
// so there is never a chance to see this has the flag and respawn it.
// Then again deletions can be delayed. Maybe it's not a problem if entities
// with 'remove' called on them are exempt from searches in the same frame?
// ehhh... let's just play it safe and do this then.
// IAWEJGFAWEIFJAWEIA
entityRemoveRespawnFlag(this);
}//END OF declareDisposable
#endif
#ifdef CSQC
class ts_powerup:CBaseEntity{
//key values?
pwuptype_choice m_ePwuptype;
int m_iPwupduration;
string m_strMessage;
// What member of ary_powerupData do I get my behavior/model/etc. from?
POWERUP_ID dataID;
BOOL alreadyPickedUp;
void(void) ts_powerup;
virtual void(void) Initialized;
virtual void(void) updateAnim;
virtual void(string strField, string strKey) SpawnKey;
virtual float(void) predraw;
//virtual void(void)Physics;
//virtual void(void)Think;
};
void ts_powerup::ts_powerup(void){
}//END OF constructor
// Use me instead for startup stuff expected after reading spawnflags.
void ts_powerup::Initialized(void){
//base method is empty, this is pointless
//CBaseEntity::Initialized();
// until the server tells us!
dataID = POWERUP_ID::NONE;
this.frame = 0;
//this.baseframe = 0;
this.alreadyPickedUp = FALSE;
this.classname = "ts_powerup";
this.owner = world;
//this.customphysics = Empty;
// no use for customphysics, right?
// so the client and server can do this? interesting.
// looks like serverside is much more common though.
// if we ever do customphysics, "frametime" may be good to use.
// also, "runstandardplayerphysics(self)". the snark's a good reference.
// not helpful here unfortunately.
//this.think = updateAnim;
//this.nextthink = time + (31-1)/50;
//ts_powerup selfRef = this;
}//END OF Initialized
void ts_powerup::updateAnim(void){
//this.lerpfrac = 1;
//this.baseframe1time = 0;
//this.frame1time = 0;
//printfline("ts_powerup::updateAnim %0.2f", this.frame1time);
//this.frame1time += clframetime;
//resets the anim frame.
this.frame1time = 0;
// Resetting the anim slightly before the marked frame end stops it from appearing to
// stall near the end.
// Unsure if this is a goldsource/FTE engine difference, but this was a nice exercise
// in a server/client interaction with an entity (or it's representative clone?)
// besides the player.
// Decals are a good source for that too though.
this.think = updateAnim;
this.nextthink = time + (31-1)/20 - 0.005;
//this.nextthink = time + 0.017;
//this.nextthink = time + 0.0;
}//END OF restartAnim
void Powerup_Parse(void){
ts_powerup selfRef;
/* convert us to an object of type decal */
// Keep in mind, this is just clientside. The server could really tell us to remove
// ourselves if needed.
if(self.classname != "ts_powerup"){
//is this really persistent?
//printfline("ts_powerup: Powerup_Parse. MY CLASSNAME IS INCORRECT: %s", self.classname);
spawnfunc_ts_powerup();
self.classname = "ts_powerup";
}
selfRef = (ts_powerup)self;
BOOL mem_alreadyPickedUp = selfRef.alreadyPickedUp;
// TEST. we probably don't need this.
// oh, not possible for the client... go figure.
//selfRef.pvsflags = PVSF_NOREMOVE;
selfRef.origin[0] = readcoord();
selfRef.origin[1] = readcoord();
selfRef.origin[2] = readcoord();
selfRef.angles[0] = readcoord();
selfRef.angles[1] = readcoord();
selfRef.angles[2] = readcoord();
selfRef.dataID = readbyte();
selfRef.alreadyPickedUp = readbyte();
BOOL isRespawnCall = readbyte();
//selfRef.dataID = readbyte(
//setmodel( entTarget, NULL );
//entTarget.modelindex = 0; //This may be redundant with above.
//selfRef.modelIndex = 0;
//selfRef.lerpfrac = 1;
selfRef.frame = 0;
//selfRef.frame1time += frametime; ???
//selfRef.frame1time = 0; resets the anim to frame 0.
//selfRef.baseframe = 0;
//selfRef.baseframe1time += 0.1;
if(isRespawnCall || (!selfRef.alreadyPickedUp && (mem_alreadyPickedUp != selfRef.alreadyPickedUp))){
// forcively respawned, or appearing since being hidden? Force animation time to 0 (start at beginning)
selfRef.frame1time = 0;
selfRef.think = ts_powerup::updateAnim;
selfRef.nextthink = time + (31-1)/20 - 0.005;
}
if(!selfRef.alreadyPickedUp){
if(mem_alreadyPickedUp != selfRef.alreadyPickedUp){
//... (script moved to above)
}
// display as usual.
setmodel( selfRef, POWERUP_MODEL_PATH);
powerupdata_basic_t powerupRef = *(ary_powerupData[selfRef.dataID]);
int mySubmodel = powerupRef.iSubmodelChoice;
setcustomskin(selfRef, "", sprintf("geomset 0 %i", mySubmodel));
}else{
// Player touched me? Blank me!
setmodel( selfRef, "" );
selfRef.modelindex = 0;
selfRef.think = NULL;
selfRef.nextthink = 0;
}
//selfRef.customphysics = ts_powerup::Physics;
//selfRef.think = ts_powerup::Think;
//selfRef.nextthink = time + 0;
// unnecessary.
//selfRef.predraw = ts_powerup::predraw;
// Why do we have to do this in Powerup_Parse and not just once in "Initialized?"
selfRef.drawmask = MASK_ENGINE;
}//END OF Powerup_Parse
float ts_powerup::predraw(void)
{
ts_powerup selfRef = (ts_powerup)self;
selfRef.frame1time += clframetime;
//adddecal(selfRef.m_strShader, selfRef.origin, selfRef.mins, selfRef.maxs, selfRef.color, 1.0f);
addentity(selfRef);
return PREDRAW_NEXT;
}
/*
void ts_powerup::Physics(void){
this.frame1time += clframetime;
}//END OF Physics
void ts_powerup::Think(void){
this.frame1time += clframetime;
this.nextthink = time + 0;
}//END OF Physics
*/
void ts_powerup::SpawnKey(string strField, string strKey)
{
//printfline("SpawnKey:: ts_powerup: (%s: %s)", strField, strKey);
switch (strField) {
case "pwuptype":
m_ePwuptype = stoi(strKey);
break;
case "pwupduration":
m_iPwupduration = stoi(strKey);
break;
case "message":m_strMessage = strKey;
break;
// Just leave this disabled, only hinders us.
// Way easier to force these things, constants work fine above.
/*
case "shader":
//m_strSprite = strKey;
//precache_pic(m_strSprite);
//m_vecSize = drawgetimagesize(m_strSprite) / 2;
break;
case "model":
//m_strSprite = sprintf("%s_0.tga", strKey);
//m_vecSize = drawgetimagesize(m_strSprite) / 2;
break;
case "scale":
//m_flScale = stof(strKey);
break;
case "rendercolor":
case "rendercolour":
//m_vecColor = stov(strKey) / 255;
break;
case "renderamt":
//m_flMaxAlpha = stof(strKey) / 255;
break;
*/
default:
CBaseEntity::SpawnKey(strField, strKey);
}
}
#endif

View file

View file

View file

View file

View file

View file

@ -0,0 +1,7 @@
// ***NOTICE! This entity will probably go completely unused.
// Likely never used by the map as it's not mentioned in ts_fgd.ffd,
// and our own logic for weapons does not use an entity anyway.
// This was likely used to represent a weapon in the player's
// inventory in the original game, since TSWorldGun is clearly the
// in-world pickup.

130
src/server/gamerules.h Normal file
View file

@ -0,0 +1,130 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
// Blended from the old FreeTS Gamerules and FreeCS, some remnants of FreeHL may be
// floating around. Most of the codebase is FreeHL inspired but this is an exception.
extern TS_GameMode currentGameMode;
extern BOOL bRule_MoneyAllowed;
extern int iRule_MaxWeightSlots;
extern BOOL bRule_GameMod_TheOne;
extern BOOL bRule_GameMod_LastManStanding;
extern int iRule_teamCount;
extern string sRule_TeamNames[4];
// Not keeping track of when to start the game,
// for recalling when the game started after it has.
extern float global_gameStartTime;
extern float global_nextBreakableRespawn;
//not meant to be customizable... yet?
#define BREAKABLE_RESPAWN_INTERVAL 60
//TAGGG - move someplace better perhaps?
#define MAX_SPAWN_POINTS 32
var entity ary_spawnStart_start[MAX_SPAWN_POINTS];
var int ary_spawnStart_start_softMax = 0;
var entity ary_spawnStart_deathmatch[MAX_SPAWN_POINTS];
var int ary_spawnStart_deathmatch_softMax = 0;
class player;
class TSGameRules:CGameRules
{
virtual void(base_player) PlayerConnect;
virtual void(base_player) PlayerDisconnect;
virtual void(base_player) PlayerKill;
virtual void(base_player) PlayerPreFrame;
virtual void(base_player) PlayerPostFrame;
virtual void(base_player) PlayerDeath;
virtual void(base_player) PlayerPain;
virtual void(base_player) LevelDecodeParms;
virtual void(base_player) LevelChangeParms;
virtual void(void) LevelNewParms;
};
class TSSingleplayerRules:TSGameRules
{
/* client */
virtual void(base_player) PlayerSpawn;
virtual void(base_player) PlayerDeath;
};
class TSMultiplayerRules:TSGameRules
{
int m_iIntermission;
int m_iIntermissionTime;
void() TSMultiplayerRules;
virtual void(void) RespawnMain;
virtual void(void) RespawnRoutine;
virtual void(void) InitPostEnts;
virtual void(void) FrameStart;
/* client */
virtual void(base_player) PlayerDisconnect;
virtual void(base_player) PlayerSpawn;
virtual void(base_player) PlayerPreFrame;
virtual void(base_player) PlayerPostFrame;
virtual void(base_player) PlayerDeath;
virtual float(base_player, string) ConsoleCommand;
virtual void(float, int) TimerBegin;
virtual void(void) TimerUpdate;
virtual void(int, int, int) RoundOver;
virtual void(int) RestartRound;
virtual void(base_player) DeathCheck;
virtual void(void) CountPlayers;
//virtual void(void) SwitchTeams;
virtual void(void) TimeOut;
virtual void(player pl) MakePlayerInvisible;
virtual void(base_player) PlayerMakePlayable;
virtual void(base_player) PlayerMakeSpectator;
virtual void(base_player pp) PlayerMakeSpectatorDelayed;
virtual void(base_player, int) PlayerRespawn;
virtual entity(float) PlayerFindSpawn;
virtual void(player pl) setPlayerMoneyDefault;
};
void Money_AddMoney(player ePlayer, int iMoneyValue);

270
src/server/gamerules.qc Normal file
View file

@ -0,0 +1,270 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
// TODO! Should all these be instance vars of the gamerules object instead of
// things floating around like this?
//default if unspecified.
//TODO - hook a bunch of this stuff up to console.
//Including a "changeteam" command if we implement teams.
var TS_GameMode currentGameMode = TS_GameMode::DEATHMATCH;
//TAGGG - INCLUSION - YOU BETTER NOT FORGET ABOUT THESE VARS.
//TAGGG TODO - there isn't one var / CVar to tell what gamemode this is? uhhh ok.
//until something deeper is made like a gamemode struct or CVar, this will have to do.
//defaults. The "81" max weight slots rule is from The Specialists as-is.
var BOOL bRule_MoneyAllowed = TRUE;
var int iRule_MaxWeightSlots = 81;
var BOOL bRule_GameMod_TheOne = FALSE;
var BOOL bRule_GameMod_LastManStanding = FALSE;
// Fill these from reading the player's team list. See notes in shared/rules.h about the
// "team list" string in Create Server options (original game)
var int iRule_teamCount = 0;
var string sRule_TeamNames[4];
var float global_gameStartTime = 0;
var float global_nextBreakableRespawn = 0;
void
TSGameRules::PlayerDeath(base_player pp)
{
}
void
TSGameRules::PlayerPain(base_player pp)
{
//TAGGG - why was this using "self" instead of the "pp"? haha.. the pp.
// Also this is completely new to FreeCS and probably FreeHL. ?
sound(pp, CHAN_VOICE, sprintf("player/pain%i.wav", randomInRange_i(1, 3)), 1, ATTN_IDLE);
}
//TAGGG - CRITICAL. Do any new player vars need to be copied too,
// same for LevelChangeParams further down?
void
TSGameRules::LevelDecodeParms(base_player pp)
{
player pl = (player)pp;
g_landmarkpos[0] = parm1;
g_landmarkpos[1] = parm2;
g_landmarkpos[2] = parm3;
pl.angles[0] = parm4;
pl.angles[1] = parm5;
pl.angles[2] = parm6;
pl.velocity[0] = parm7;
pl.velocity[1] = parm8;
pl.velocity[2] = parm9;
pl.g_items = parm10;
pl.activeweapon = parm11;
pl.flags = parm64;
//TAGGG - why was this one missing from FreeHL? Present in FreeCS.
pl.gflags = parm63;
/*
pl.ammo_9mm = parm12;
pl.ammo_357 = parm13;
pl.ammo_buckshot = parm14;
pl.ammo_m203_grenade = parm15;
pl.ammo_bolt = parm16;
pl.ammo_rocket = parm17;
pl.ammo_uranium = parm18;
pl.ammo_handgrenade = parm19;
pl.ammo_satchel = parm20;
pl.ammo_tripmine = parm21;
pl.ammo_snark = parm22;
pl.ammo_hornet = parm23;
pl.glock_mag = parm24;
pl.mp5_mag = parm25;
pl.python_mag = parm26;
pl.shotgun_mag = parm27;
pl.crossbow_mag = parm28;
pl.rpg_mag = parm29;
pl.satchel_chg = parm30;
*/
if (pl.flags & FL_CROUCHING) {
setsize(pl, VEC_CHULL_MIN, VEC_CHULL_MAX);
} else {
setsize(pl, VEC_HULL_MIN, VEC_HULL_MAX);
}
}
void
TSGameRules::LevelChangeParms(base_player pp)
{
player pl = (player)pp;
parm1 = g_landmarkpos[0];
parm2 = g_landmarkpos[1];
parm3 = g_landmarkpos[2];
parm4 = pl.angles[0];
parm5 = pl.angles[1];
parm6 = pl.angles[2];
parm7 = pl.velocity[0];
parm8 = pl.velocity[1];
parm9 = pl.velocity[2];
//TAGGG - why was this one missing from FreeHL? Present in FreeCS.
parm63 = pl.gflags;
parm64 = pl.flags;
parm10 = pl.g_items;
parm11 = pl.activeweapon;
/*
parm12 = pl.ammo_9mm;
parm13 = pl.ammo_357;
parm14 = pl.ammo_buckshot;
parm15 = pl.ammo_m203_grenade;
parm16 = pl.ammo_bolt;
parm17 = pl.ammo_rocket;
parm18 = pl.ammo_uranium;
parm19 = pl.ammo_handgrenade;
parm20 = pl.ammo_satchel;
parm21 = pl.ammo_tripmine;
parm22 = pl.ammo_snark;
parm23 = pl.ammo_hornet;
parm24 = pl.glock_mag;
parm25 = pl.mp5_mag;
parm26 = pl.python_mag;
parm27 = pl.shotgun_mag;
parm28 = pl.crossbow_mag;
parm29 = pl.rpg_mag;
parm30 = pl.satchel_chg;
*/
}
void
TSGameRules::LevelNewParms(void)
{
//TAGGG - parm63 added, absent in FreeHL.
parm1 = parm2 = parm3 = parm4 = parm5 = parm6 = parm7 =
parm8 = parm9 = parm10 = parm11 = parm12 = parm13 = parm14 =
parm15 = parm16 = parm17 = parm18 = parm19 = parm20 = parm21 =
parm22 = parm23 = parm24 = parm25 = parm26 = parm27 = parm28 =
parm29 = parm30 = parm63 = 0;
parm64 = FL_CLIENT;
}
//TAGGG - NEW. Need PlayerPreFrame as well.
void
TSGameRules::PlayerPreFrame(base_player pp)
{
//TAGGG - NEW
// Is this null check redundant?
if(pp != NULL){
player pl = (player)pp;
pl.preThink();
}
}
/* we check what fields have changed over the course of the frame and network
* only the ones that have actually changed */
void
TSGameRules::PlayerPostFrame(base_player pp)
{
//TAGGG - NEW
// Is this null check redundant?
if(pp != NULL){
player pl = (player)pp;
pl.postThink();
}
}
void
TSGameRules::PlayerConnect(base_player pl)
{
if (Plugin_PlayerConnect(pl) == FALSE)
bprint(PRINT_HIGH, sprintf("%s connected\n", pl.netname));
}
void
TSGameRules::PlayerDisconnect(base_player pl)
{
if (Plugin_PlayerDisconnect(pl) == FALSE)
bprint(PRINT_HIGH, sprintf("%s disconnected\n", pl.netname));
/* Make this unusable */
pl.solid = SOLID_NOT;
pl.movetype = MOVETYPE_NONE;
pl.modelindex = 0;
pl.health = 0;
pl.takedamage = 0;
//TAGGG
// FreeHL way.
pl.SendFlags = -1;
// why does FreeCS do this instead? Check for mentions of PLAYER_MODELINDEX
// in FreeCS that aren't in FreeHL maybe.
//pl.SendFlags = PLAYER_MODELINDEX;
}
void
TSGameRules::PlayerKill(base_player pl)
{
//because we are TS.
//Damage_Apply(pl, pl, pl.health, WEAPON_NONE, DMG_SKIP_ARMOR);
Damage_Apply(pl, pl, pl.health, WEAPON_ID::NONE, DMG_SKIP_ARMOR);
}
void
Money_AddMoney(player pl, int iMoneyValue)
{
//dprint(sprintf("^2Money_AddMoney^7: giving %s $%i\n", pl.netname, iMoneyValue));
pl.money += iMoneyValue;
if (pl.money > autocvar_fcs_maxmoney) {
pl.money = autocvar_fcs_maxmoney;
}
if (pl.money < 0) {
pl.money = 0;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,116 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//TAGGG - Although TS is not very singleplayer-friendly, could this still use
// some things like the corpse fade think call instead? Being a copy of FreeHL is just sloppy.
// Heck just have stuff not dependent on being single/multiplayer moved to TSGameRules::PlayerDeath
// instead, 99% of the stuff does not care about being multiplayer.
void
TSSingleplayerRules::PlayerDeath(base_player pp)
{
TSGameRules::PlayerDeath(pp);
player pl = (player)pp;
pp.movetype = MOVETYPE_NONE;
pp.solid = SOLID_NOT;
pp.takedamage = DAMAGE_NO;
//pp.gflags &= ~GF_FLASHLIGHT;
//TAGGG - good idea too?
pl.setInventoryEquippedIndex(-1);
pp.armor = pp.activeweapon = pp.g_items = pp.weapon = 0;
pp.health = 0;
Sound_Play(pp, CHAN_AUTO, "player.die");
if (cvar("coop") == 1) {
pp.think = PutClientInServer;
pp.nextthink = time + 4.0f;
}
if (pp.health < -50) {
FX_GibHuman(pp.origin);
}
/* Let's handle corpses on the clientside */
entity corpse = spawn();
setorigin(corpse, pp.origin + [0,0,32]);
setmodel(corpse, pp.model);
setsize(corpse, VEC_HULL_MIN, VEC_HULL_MAX);
corpse.movetype = MOVETYPE_TOSS;
corpse.solid = SOLID_TRIGGER;
corpse.modelindex = pp.modelindex;
corpse.frame = ANIM_DIESIMPLE;
corpse.angles = pp.angles;
corpse.velocity = pp.velocity;
}
void
TSSingleplayerRules::PlayerSpawn(base_player pp)
{
pp.classname = "player";
pp.health = pp.max_health = 100;
pp.takedamage = DAMAGE_YES;
pp.solid = SOLID_SLIDEBOX;
pp.movetype = MOVETYPE_WALK;
pp.flags = FL_CLIENT;
pp.viewzoom = 1.0;
pp.model = "models/player.mdl";
if (cvar("coop") == 1) {
string mymodel = infokey(pp, "model");
if (mymodel) {
mymodel = sprintf("models/player/%s/%s.mdl", mymodel, mymodel);
if (whichpack(mymodel)) {
pp.model = mymodel;
}
}
}
setmodel(pp, pp.model);
setsize(pp, VEC_HULL_MIN, VEC_HULL_MAX);
pp.velocity = [0,0,0];
pp.gravity = __NULL__;
pp.frame = 1;
//pp.SendEntity = Player_SendEntity;
pp.SendFlags = UPDATE_ALL;
pp.customphysics = Empty;
pp.iBleeds = TRUE;
forceinfokey(pp, "*spec", "0");
forceinfokey(pp, "*deaths", ftos(pp.deaths));
/* this is where the mods want to deviate */
entity spot;
if (startspot != "") {
dprint(sprintf("^3Gamerules_Spawn^7: Startspot is %s\n", startspot));
LevelDecodeParms(pp);
setorigin(pp, Landmark_GetSpot());
} else {
LevelNewParms();
spot = find(world, ::classname, "info_player_start");
setorigin(pp, spot.origin);
pp.angles = spot.angles;
}
Weapons_RefreshAmmo(pp);
Client_FixAngle(pp, pp.angles);
}

315
src/server/init.qc Normal file
View file

@ -0,0 +1,315 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//TAGGG - file renamed from server.qc to init.qc.
// Has only init methods like client/init.qc, so naming after that.
/*
// DEMO! So 'enumflags' does mean they count up in powers of 2,
// so starting at 1, then 2, 4, 8, 16, 32, etc.
// (starting at 1 since 2^0 is 1; each spot is 2^i)
// typedef + name after not required, but do-able.
// And floats because FTE. Or Quake C, one of those.
typedef enumflags
{
TEST_ENUM_1,
TEST_ENUM_2,
TEST_ENUM_3,
TEST_ENUM_4,
TEST_ENUM_5
} myFlags_t;
*/
void Game_Spawn_ObserverCam(player pl){
//printfline("Game_Spawn_ObserverCam: ary_spawnStart_start_softMax: %i", ary_spawnStart_start_softMax);
if(ary_spawnStart_start_softMax == 0){
printfline("Game_Spawn_ObserverCam: no recorded spawn locations");
//printfline("MY VALS! %d %d %d %d %d", TEST_ENUM_1, TEST_ENUM_2, TEST_ENUM_3, TEST_ENUM_4, TEST_ENUM_5);
initSpawnMem();
}
// force randomSpawnChoice to a constant to spawn in the same place always if needed
int randomSpawnChoice = randomInRange_i(0, ary_spawnStart_deathmatch_softMax-1);
entity eCamera = ary_spawnStart_deathmatch[randomSpawnChoice];
entity eTarget;
if (eCamera) {
pl.origin = eCamera.origin;
if (eCamera.target) {
eTarget = find(world, ::targetname, eCamera.target);
if (eTarget) {
pl.angles = vectoangles(eTarget.origin - eCamera.origin);
pl.angles[0] *= -1;
}
}else{
// no target? What else do we have?
//entity eTarget;
//self.angles = eCamera.angles;
pl.angles = eCamera.angles;
// Why do we have to do this now? No clue.
// something about animation.h maybe? I Forget.
pl.v_angle = pl.angles;
Client_FixAngle(pl, pl.angles);
}
} else {
// Can't find a camera? Just do this lazy thing, CS seems to do the same
eCamera = find (world, ::classname, "info_player_start");
if (eCamera) {
pl.origin = eCamera.origin;
if (eCamera.target) {
eTarget = find(world, ::targetname, eCamera.target);
if (eTarget) {
pl.angles = vectoangles(eTarget.origin - eCamera.origin);
pl.angles[0] *= -1;
}
}
}
}
// TAGGG - NEW LINE. do we need to do this?
setorigin(pl, pl.origin);
Client_FixAngle(pl, pl.angles);
}//END OF Game_Spawn_ObserverCam
//TAGGG - NOTE. Should this get a 0.1 delay like nuclide's src/server/entry.qc does here:
// entity respawntimer = spawn();
// respawntimer.think = init_respawn;
// respawntimer.nextthink = time + 0.1f;
// Might not be necessary. Or compare better with old FreeTS, just in case the new way of calling
// (straight from Game_Worldspawn) doesn't work.
// This needs the other info_<whatever> entities to exist for keeping track of places to spawn,
// being incomplete at the time of this call is unacceptable.
void initSpawnMem(void){
//printfline("initSpawnMem");
// two spawn-related entities present in the map:
// info_player_start
// info_player_deathmatch
ary_spawnStart_start_softMax = 0;
ary_spawnStart_deathmatch_softMax = 0;
for(entity eFind = world; (eFind = find(eFind, classname, "info_player_start"));){
if(ary_spawnStart_start_softMax == MAX_SPAWN_POINTS){
printfline("ERROR!! Map exceeds maximum of %d info_player_start points. Further ones ignored.", MAX_SPAWN_POINTS);
break;
}
//printfline("info_player_start: found one: %s", eFind.classname);
ary_spawnStart_start[ary_spawnStart_start_softMax] = eFind;
ary_spawnStart_start_softMax++;
}
for(entity eFind = world; (eFind = find(eFind, classname, "info_player_deathmatch"));){
if(ary_spawnStart_deathmatch_softMax == MAX_SPAWN_POINTS){
printfline("ERROR!! Map exceeds maximum of %d info_player_deathmatch points. Further ones ignored.", MAX_SPAWN_POINTS);
break;
}
//printfline("info_player_deathmatch: found %s", eFind.classname);
ary_spawnStart_deathmatch[ary_spawnStart_deathmatch_softMax] = eFind;
ary_spawnStart_deathmatch_softMax++;
}
printfline("initSpawnMem: how many spawns, info_p_start:%i info_p_deathmatch:%i", ary_spawnStart_start_softMax, ary_spawnStart_deathmatch_softMax);
}
void
Game_InitRules(void)
{
if (cvar("sv_playerslots") == 1 || cvar("coop") == 1) {
g_grMode = spawn(TSSingleplayerRules);
} else {
g_grMode = spawn(TSMultiplayerRules);
}
}
// Effectvely, ServerGame_Init
void
Game_Worldspawn(void)
{
printfline("***ServerGame_Init (Game_Worldspawn) called***");
//TAGGG - NEW
SharedGame_Init();
ServerGame_Precache();
// calls each weapon's .precache method.
Weapons_Init();
Player_Precache();
//TAGGG - new stuff
string sTemp;
int iMOTDLines = 0;
printfline("Game_Worldspawn");
// The message of the day.
// This saves the contents of the MoTD text file (path given by CVar motdfile) to a series of
// serverinfo entries named motdline0, 1, 2, ... for reading back in client/vgui.c from the server
// at client-connect time (CSQC_VGUI_Init) to go in the MoTD window whenever needed.
localcmd(sprintf("echo [MOTD] Loading %s.\n", autocvar_motdfile));
filestream fmMOTD = fopen(autocvar_motdfile, FILE_READ);
if (fmMOTD >= 0) {
for (int i = 0; i < 25; i++) {
sTemp = fgets(fmMOTD);
if not (sTemp) {
break;
}
if (sTemp == NULL) {
localcmd(sprintf("serverinfo motdline%i /\n", iMOTDLines));
} else {
localcmd(sprintf("serverinfo motdline%i %s\n", iMOTDLines, sTemp));
}
iMOTDLines++;
}
localcmd(sprintf("serverinfo motdlength %i\n", iMOTDLines));
fclose(fmMOTD);
} else {
error("[MOTD] Loading failed.\n");
}
//TAGGG - REMOVED! Nuclide now handles mapcycle
/*
// The mapcycle information.
localcmd(sprintf("echo [MAPCYCLE] Loading %s.\n", autocvar_mapcyclefile));
filestream fmMapcycle = fopen(autocvar_mapcyclefile, FILE_READ);
if (fmMapcycle >= 0) {
for (int i = 0;; i++) {
sTemp = fgets(fmMapcycle);
if not (sTemp) {
break;
}
if (sTemp != NULL) {
iMapCycleCount++;
}
}
fseek(fmMapcycle, 0);
localcmd(sprintf("echo [MAPCYCLE] List has %i maps.\n", iMapCycleCount));
sMapCycle = memalloc(sizeof(string) * iMapCycleCount);
for (int i = 0; i < iMapCycleCount; i++) {
sMapCycle[i] = fgets(fmMapcycle);
}
fclose(fmMapcycle);
for (int i = 0; i < iMapCycleCount; i++) {
if (sMapCycle[i] == mapname) {
if ((i + 1) < iMapCycleCount) {
localcmd(sprintf("echo [MAPCYCLE] Next map: %s\n", sMapCycle[i + 1]));
} else {
break;
}
}
}
} else {
iMapCycleCount = 0;
error("[MAPCYCLE] Loading failed.\n");
}
*/
// Let's make our version information clear
localcmd(sprintf("serverinfo fcs_ver %s\n", __DATE__));
/*Bot_Init();*/
//How many weight slots does this player have?
//...oh wait, we already receive whatever the actual playerSlots is, even if the inventorylogic
// clientside never had to refer to it.
// That is, pl.iTotalSlots is set by serverside when we spawn with a config (filled by whatever
// weapons are applied).
// Then, serverside pl.iTotalSlots gets written to clientside pl.iTotalSlots too each frame.
//pointerstat(STAT_WEIGHTSLOTS, EV_FLOAT, iTotalSlots);
//TAGGG - INCLUSION. NEW.
pointerstat(STAT_RULE_MONEYALLOWED, EV_INTEGER, &bRule_MoneyAllowed);
pointerstat(STAT_RULE_MAXWEIGHTSLOTS, EV_INTEGER, &iRule_MaxWeightSlots);
//iBombRadius = 1024;
localcmd(sprintf("serverinfo slots %d\n", cvar("sv_playerslots")));
localcmd("teamplay 1\n");
//TAGGG - shouldn't these get explicit defaults?
g_ts_gamestate = GAME_INACTIVE;
g_ts_gametime = 0;
// Go ahead, setup some info about all possible locations for players to spawn.
// All entities have loaded at this point, right? Sure they have for this to be Game_Worldspawn,
// see what calls that if uncertain from nuclide.
initSpawnMem();
//TAGGG - 2020update?
/*
// and what do these mean anyway? Search game files, maybe something tells what "buy.kevlar" means?
// how to derive that to some sound file I assume?
Sound_Precache("buy.kevlar");
Sound_Precache("buy.weapon");
Sound_Precache("buy.ammo");
Weapons_Init();
clientstat(STAT_MONEY, EV_INTEGER, player::money);
*/
clientstat(STAT_MONEY, EV_INTEGER, player::money);
pointerstat(STAT_GAMETIME, EV_FLOAT, &g_ts_gametime);
pointerstat(STAT_GAMESTATE, EV_INTEGER, &g_ts_gamestate);
}

30
src/server/items.h Normal file
View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* PICKUP ITEMS */
class item_pickup:CBaseTrigger
{
int m_bFloating;
int m_iClip;
int m_iWasDropped;
int id;
void(void) item_pickup;
virtual void(void) touch;
virtual void(int i) SetItem;
virtual void(void) Respawn;
virtual void(int) SetFloating;
};

88
src/server/items.qc Normal file
View file

@ -0,0 +1,88 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
void item_pickup::touch(void)
{
if (other.classname != "player") {
return;
}
/* don't remove if AddItem fails */
if (Weapons_AddItem((player)other, id, m_iClip) == FALSE) {
return;
}
Logging_Pickup(other, this, __NULL__);
Sound_Play(other, CHAN_ITEM, "weapon.pickup");
UseTargets(other, TRIG_TOGGLE, m_flDelay);
if (real_owner || m_iWasDropped == 1 || cvar("sv_playerslots") == 1) {
remove(self);
} else {
Hide();
think = Respawn;
nextthink = time + 30.0f;
}
}
void item_pickup::SetItem(int i)
{
id = i;
m_oldModel = Weapons_GetWorldmodel(id);
SetModel(m_oldModel);
SetSize([-16,-16,0], [16,16,16]);
}
void item_pickup::SetFloating(int i)
{
m_bFloating = rint(bound(0, m_bFloating, 1));
}
void item_pickup::Respawn(void)
{
SetSolid(SOLID_TRIGGER);
SetOrigin(m_oldOrigin);
/* At some points, the item id might not yet be set */
if (m_oldModel) {
SetModel(m_oldModel);
}
SetSize([-16,-16,0], [16,16,16]);
think = __NULL__;
nextthink = -1;
if (!m_iWasDropped && cvar("sv_playerslots") > 1) {
if (!real_owner && time > 30.0f)
Sound_Play(this, CHAN_ITEM, "item.respawn");
m_iClip = -1;
}
if (!m_bFloating) {
droptofloor();
SetMovetype(MOVETYPE_TOSS);
}
}
void item_pickup::item_pickup(void)
{
Sound_Precache("item.respawn");
Sound_Precache("weapon.pickup");
CBaseTrigger::CBaseTrigger();
}

18
src/server/player.h Normal file
View file

@ -0,0 +1,18 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
void Player_UseDown(void);
void Player_UseUp(void);

185
src/server/player.qc Normal file
View file

@ -0,0 +1,185 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
====================
UseWorkaround
====================
*/
void UseWorkaround(entity eTarget)
{
eActivator = self;
entity eOldSelf = self;
self = eTarget;
self.PlayerUse();
self = eOldSelf;
}
/*
====================
Player_UseDown
====================
*/
void Player_UseDown(void)
{
vector vecSrc;
//TAGGG - CHECK. Does the "self.flags & FL_USE_RELEASED" check need to be commented out?
// Old way has it that way
if (self.health <= 0) {
return;
} else if (!(self.flags & FL_USE_RELEASED)) {
return;
}
makevectors(self.v_angle);
vecSrc = self.origin + self.view_ofs;
player pl = (player)self;
//TAGGG - modern FreeHL way. Replacing it with my own completely for now
/*
self.hitcontentsmaski = CONTENTBITS_POINTSOLID;
traceline(vecSrc, vecSrc + (v_forward * 64), MOVE_HITMODEL, self);
if (trace_ent.PlayerUse) {
self.flags &= ~FL_USE_RELEASED;
UseWorkaround(trace_ent);
// Some entities want to support Use spamming
if (!(self.flags & FL_USE_RELEASED)) {
sound(self, CHAN_ITEM, "common/wpn_select.wav", 0.25, ATTN_IDLE);
}
} else {
sound(self, CHAN_ITEM, "common/wpn_denyselect.wav", 0.25, ATTN_IDLE);
self.flags &= ~FL_USE_RELEASED;
}
*/
BOOLEAN isPickup;
//TAGGG - point of interest!
// Old way for the 2nd to last param here was MOVE_NOMONSTERS (1).
// FreeHL's way is now MOVE_HITMODEL. Trying that out, revert if needed
traceline ( vecSrc, vecSrc + ( v_forward * 36 ), MOVE_HITMODEL, self );
entity ef = findradius(trace_endpos, 40);
while (ef) {
//printfline("An entity found: %s has use? %d", ef.classname, (ef.PlayerUse!=NULL) );
if (ef.PlayerUse != NULL) {
vector dirTowards = normalize(ef.origin - self.origin);
//TODO - how do we obtain this. v_forward from make angles??
vector dirFacing = v_forward;
// cheap check for now, this is the only combo that has both
// a class of "remove_me" and a 'PlayerUse' method set.
isPickup = (ef.classname == "remove_me");
//isPickup = FALSE;
if(isPickup){
//woohoo.
//just keep trying use on anything we can.
UseWorkaround(ef);
//return;
}else{
float prod = dotproduct(dirTowards, dirFacing);
float dist = vlen(ef.origin - self.origin);
//printfline("use dotprod:%.2f dist:%.2f", prod, dist);
if(prod >= 0.97 && dist < 52){
UseWorkaround(ef);
}
}
}
ef = ef.chain;
}
//printfline("self.mins.z? %.2f", self.mins.z);
//search around where the player is standing too.
ef = findradius(self.origin + [0,0,self.mins.z], 30);
while (ef) {
if (ef.PlayerUse != NULL) {
isPickup = (ef.classname == "remove_me");
//isPickup = FALSE;
if(isPickup){
UseWorkaround(ef);
}
//return;
}
ef = ef.chain;
}
self.flags &= ~FL_USE_RELEASED;
}
/*
====================
Player_UseUp
====================
*/
void Player_UseUp(void) {
if (!(self.flags & FL_USE_RELEASED)) {
self.flags |= FL_USE_RELEASED;
}
}
// check out TS_playerEquippedWeapon, it already does this
/*
//TAGGG - CRITICAL! Make this work better with the inventory system.
// Changing activeweapon directly is meaningless to us, changing
/// inventoryEquippedIndex makes more sense, then activeweapon from that
// ...unless we trust that is handled anyway, but I doubt that.
void CSEv_PlayerSwitchWeapon_i(int w)
{
player pl = (player)self;
//pl.activeweapon = w;
pl.setInventoryEquippedIndex(w);
Weapons_Draw();
}
*/
void
Player_Precache(void)
{
searchhandle pm;
pm = search_begin("models/player/*/*.mdl", TRUE, TRUE);
for (int i = 0; i < search_getsize(pm); i++) {
precache_model(search_getfilename(pm, i));
}
search_end(pm);
}

2
src/server/precache.h Normal file
View file

@ -0,0 +1,2 @@
void ServerGame_Precache(void);

142
src/server/precache.qc Normal file
View file

@ -0,0 +1,142 @@
void ServerGame_Precache(void){
printfline("***ServerGame_Precache called***");
SharedGame_Precache();
//TAGGG - From FreeHL. Anything that ends up unused by FreeTS can be removed,
// including sound config files that are then unused.
////////////////////////////////////////////////////////////
Sound_Precache("player.die");
Sound_Precache("player.die_headshot");
Sound_Precache("player.fall");
Sound_Precache("player.lightfall");
precache_model("models/player.mdl");
// no.
//precache_model("models/w_weaponbox.mdl");
////////////////////////////////////////////////////////////
//precache_sound("weapons/pistol-empty.wav");
precache_sound("weapons/fnh/fnh-fire.wav");
//counterstikre leftovers... remove ASAP.
precache_sound("weapons/knife_slash1.wav");
precache_sound("weapons/knife_slash2.wav");
precache_sound("weapons/ak47-1.wav");
precache_sound("weapons/ak47-2.wav");
precache_model("models/powerup.mdl");
// player models in cstrike are precached
// serverside only, so we can too I suppose
precache_model("models/player/agent/agent.mdl");
precache_model("models/player/castor/castor.mdl");
precache_model("models/player/gordon/gordon.mdl");
precache_model("models/player/hitman/hitman.mdl");
precache_model("models/player/laurence/laurence.mdl");
precache_model("models/player/merc/merc.mdl");
precache_model("models/player/seal/seal.mdl");
for(int i = 1; i < WEAPON_ID::LAST_ID; i++){
if(ary_weaponData[i] != NULL){
weapondata_basic_t* basicPointer = (weapondata_basic_t*) ary_weaponData[i];
weapondata_basic_t basicRef = *(basicPointer);
if(basicRef.sViewModelPath != NULL){
precache_model(basicRef.sViewModelPath);
}
if(basicRef.sPlayerModelPath != NULL){
precache_model(basicRef.sPlayerModelPath);
}
if(basicRef.sPlayerSilencerModelPath != NULL){
precache_model(basicRef.sPlayerSilencerModelPath);
}
if(basicRef.sWorldModelPath != NULL){
//printfline("I PRECACHED THIS %s", basicRef.sWorldModelPath);
precache_model(basicRef.sWorldModelPath);
}
//precache_model(basicRef.sIconFilePath);
}
}
for(int i = 1; i < WEAPON_AKIMBO_UPGRADE_ID::LAST_ID; i++){
if(ary_akimboUpgradeData[i] != NULL){
weapondata_basic_t* basicPointer = (weapondata_basic_t*) ary_akimboUpgradeData[i];
weapondata_basic_t basicRef = *(basicPointer);
if(basicRef.sViewModelPath != NULL){
precache_model(basicRef.sViewModelPath);
}
if(basicRef.sPlayerModelPath != NULL){
precache_model(basicRef.sPlayerModelPath);
}
if(basicRef.sPlayerSilencerModelPath != NULL){
precache_model(basicRef.sPlayerSilencerModelPath);
}
if(basicRef.sWorldModelPath != NULL){
//printfline("I PRECACHED THIS %s", basicRef.sWorldModelPath);
precache_model(basicRef.sWorldModelPath);
}
//precache_model(basicRef.sIconFilePath);
}
}
// do 3D models too.
precache_model("models/ammopack.mdl");
precache_model("sprites/glow01.spr");
precache_sound("player/die1.wav");
precache_sound("player/die2.wav");
precache_sound("player/die3.wav");
precache_sound("player/headshot.wav");
precache_sound("player/headshot2.wav");
precache_sound("player/bodyhit1.wav");
precache_sound("player/bodyhit2.wav");
precache_sound("player/bodyhit3.wav");
precache_sound("player/bodyhit4.wav");
precache_sound("player/pain1.wav");
precache_sound("player/pain2.wav");
precache_sound("player/pain3.wav");
precache_sound("player/pain4.wav");
precache_sound("weapons/grenbounce1.wav");
precache_sound("weapons/grenbounce2.wav");
precache_sound("weapons/grenbounce3.wav");
precache_sound("weapons/grenbounce4.wav");
precache_sound("explo/explode.wav");
precache_sound("explo/explode1.wav");
precache_sound("explo/explode2.wav");
precache_sound("explo/explode3.wav");
precache_sound("explo/explode4.wav");
precache_sound("goslow.wav");
//Other weapons related... should this be in "shared" instead?
precache_sound("weapons/ak47/fire.wav");
precache_sound("weapons/aug/aug-fire.wav");
precache_sound("weapons/aug/aug-fire-sil.wav");
precache_sound("weapons/barrett/fire.wav");
//...ETC
}

132
src/server/progs.src Normal file
View file

@ -0,0 +1,132 @@
#pragma target fte
#pragma progs_dat "../../progs.dat"
#define QWSSQC
#define SERVER
#define TS
//TAGGG - NEW. TS definitely has penetration, and so should this then.
#define BULLETPENETRATION
//TAGGG - NEW. FreeCS also has this. Do we want/need it? Keeping for now.
// NEVERMIND, requires a new var "cs_shotmultiplier" and any other prediction-related tie-ins as seen
// in freeCS's shared/player.qc.
// Would like to know what BULLETPATTERNS even does to see if FreeTS had anything like that.
//#define BULLETPATTERNS
#define GS_RENDERFX
// FORCE A SPAWN LOC. Sometimes easier for testing to have a constant set of spawn coords/angle.
// Use with the right start params to jump into the map at debug.
//#define TS_CUSTOM_SPAWN_ORIGIN '21 -1291 -380'
//#define TS_CUSTOM_SPAWN_ORIGIN '-754 569 -380'
// THE POOL
#define TS_CUSTOM_SPAWN_ORIGIN '-458.5 957.7 -380.0'
#define TS_CUSTOM_SPAWN_VANGLE [0.0, 45.0, 0.0]
// in the house
//#define TS_CUSTOM_SPAWN_ORIGIN [93.1, -80.0, -380.0]
//#define TS_CUSTOM_SPAWN_VANGLE [0.0, 90.0, 0.0]
// ts_awaken: the ledge thingy.
//#define TS_CUSTOM_SPAWN_ORIGIN [12.4, -1350.6, -292.0]
//#define TS_CUSTOM_SPAWN_VANGLE [-12.5, 0.5, 0.0]
#includelist
../../../src/shared/fteextensions.qc
../../../src/gs-entbase/server/defs.h
../../../src/shared/defs.h
../../../src/server/defs.h
../../../src/gs-entbase/server.src
../../../src/gs-entbase/shared.src
//TAGGG - NEW
../shared/util.h
../shared/defs.h
defs.h
../shared/ammo.h
../shared/weapons.h
../shared/player.h
../shared/powerup.h
precache.h
entity/ts_powerup.h
//player.c
entity/TSWorldGun.h
entity/TSThrownProjectile.h
../shared/include.src
// <HL monster includes were here>
//TAGGG - NEW
entity/ts_bomb.qc
entity/ts_dmhill.qc
entity/ts_groundweapon.qc
entity/ts_hack.qc
entity/ts_mapglobals.qc
entity/ts_model.qc
entity/ts_objective_manager.qc
entity/ts_objective_ptr.qc
entity/ts_powerup.qc
entity/ts_slowmotion.qc
entity/ts_slowmotionpoint.qc
entity/ts_teamescape.qc
entity/ts_trigger.qc
entity/ts_wingiver.qc
entity/TSWorldGun.qc
entity/TSThrownProjectile.qc
entity/TSAmmoPack.qc
../shared/player.qc
../shared/inventory_logic.qc
player.qc
spectator.qc
items.qc
world_items.qc
../../../src/botlib/include.src
gamerules.qc
gamerules_singleplayer.qc
gamerules_multiplayer.qc
client.qc
init.qc
// NOPE! Using our own copy now
//../../../base/src/server/damage.qc
damage.qc
rules.qc
../../../base/src/server/modelevent.qc
spawn.qc
//TAGGG - NEW
// no server util.h/.qc, yet?
//util.qc
precache.qc
//money.qc
//spectator.qc
../../../src/server/include.src
../../../src/shared/include.src
#endlist

20
src/server/rules.qc Normal file
View file

@ -0,0 +1,20 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
int Rules_IsTeamPlay(void)
{
return cvar("teamplay");
}

41
src/server/spawn.qc Normal file
View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
void info_player_start(void)
{
self.solid = SOLID_TRIGGER;
setsize(self, VEC_HULL_MIN, VEC_HULL_MAX);
self.botinfo = BOTINFO_SPAWNPOINT;
}
void info_player_deathmatch(void)
{
self.solid = SOLID_TRIGGER;
setsize(self, VEC_HULL_MIN, VEC_HULL_MAX);
self.botinfo = BOTINFO_SPAWNPOINT;
}
void info_player_team1(void)
{
self.classname = "info_player_deathmatch";
self.botinfo = BOTINFO_SPAWNPOINT;
}
void info_player_team2(void)
{
self.classname = "info_player_deathmatch";
self.botinfo = BOTINFO_SPAWNPOINT;
}

28
src/server/spectator.qc Normal file
View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
void Game_SpectatorThink(void)
{
}
void Game_SpectatorConnect(void)
{
}
void Game_SpectatorDisconnect(void)
{
}

49
src/server/world_items.qc Normal file
View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//TAGGG - PENDING DELETE! I think this is useless to FreeTS?
/* This is one of those leftovers from trying to get a game out in time */
class world_items:CBaseTrigger
{
void(void) world_items;
};
void world_items::world_items(void)
{
/*
int nfields = tokenize(__fullspawndata);
for (int i = 1; i < (nfields - 1); i += 2) {
switch (argv(i)) {
case "type":
float type = stof(argv(i+1));
switch (type) {
case 44:
spawnfunc_item_battery();
break;
case 45:
spawnfunc_item_suit();
break;
default:
}
default:
break;
}
}
*/
}

39
src/shared/ammo.h Normal file
View file

@ -0,0 +1,39 @@
enum AMMO_ID{
NONE = 0,
_9x19mm = 1,
_45Acp = 2,
_p50AE = 3,
_5p7x28 = 4,
_10mmAUTO = 5,
_p22LR = 6,
_p454Casull = 7,
_5p56Nato = 8,
_7p62x39mm = 9,
_50BMG = 10,
_SHELLS = 11,
//needed for the cut weapons from TS 2.0
_32ACP = 12,
_7p62x51mm = 13,
LAST_ID = 14
};
#define ASSIGN_AMMODATA(arg_constName) ary_ammoData[AMMO_ID::##arg_constName] = (ammodata_t*) &ammo_##arg_constName;
#define DECLARE_AMMODATA(arg_varName, arg_sDisplayName, arg_fPricePerBullet, arg_iMax) ammodata_t ammo_##arg_varName = {arg_sDisplayName, arg_fPricePerBullet, arg_iMax};
typedef struct{
string sDisplayName;
float fPricePerBullet;
int iMax;
// fPricePerBullet * iMax = total cost to fill (added for the first weapon of a given ammo type purchased)
} ammodata_t;

35
src/shared/ammo.qc Normal file
View file

@ -0,0 +1,35 @@
DECLARE_AMMODATA(NONE, "_NONE_", 0, 0)
DECLARE_AMMODATA(_9x19mm, "9 x 19mm", 1.6666666666, 210)
DECLARE_AMMODATA(_45Acp, ".45Acp", 5.1, 200)
DECLARE_AMMODATA(_p50AE, ".50AE", 7, 70)
DECLARE_AMMODATA(_5p7x28, "5.7 x 28", 6, 200)
DECLARE_AMMODATA(_10mmAUTO, "10mm AUTO", 4.9, 100)
DECLARE_AMMODATA(_p22LR, ".22 LR", 6, 150)
DECLARE_AMMODATA(_p454Casull, ".454 Casull", 30, 50)
DECLARE_AMMODATA(_5p56Nato, "5.56 Nato", 2.6666666666, 90)
DECLARE_AMMODATA(_7p62x39mm, "7.62 x 39mm", 3.3333333333, 90)
DECLARE_AMMODATA(_50BMG, "50BMG", 100, 20)
DECLARE_AMMODATA(_SHELLS, "SHELLS", 16, 60)
DECLARE_AMMODATA(_32ACP, ".32 ACP", 0.5, 300)
DECLARE_AMMODATA(_7p62x51mm, "7.62 x 51mm", 30, 35)

95
src/shared/animations.h Normal file
View file

@ -0,0 +1,95 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
enum
{
ANIM_LOOKIDLE,
ANIM_IDLE,
ANIM_DEEPIDLE,
ANIM_RUN2,
ANIM_WALK2HANDED,
ANIM_2HANDSHOT,
ANIM_CRAWL,
ANIM_CROUCHIDLE,
ANIM_JUMP,
ANIM_LONGJUMP,
ANIM_SWIM,
ANIM_TREADWATER,
ANIM_RUN,
ANIM_WALK,
ANIM_AIM2,
ANIM_SHOOT2,
ANIM_AIM1,
ANIM_SHOOT1,
ANIM_DIESIMPLE,
ANIM_DIEBACKWARDS1,
ANIM_DIEBACKWARDS2,
ANIM_DIEFORWARD,
ANIM_DIEHEADSHOT,
ANIM_DIESPIN,
ANIM_DIEGUTSHOT,
ANIM_AIMCROWBAR,
ANIM_SHOOTCROWBAR,
ANIM_CR_AIMCROWBAR,
ANIM_CR_SHOOTCROWBAR,
ANIM_AIMTRIPMINE,
ANIM_SHOOTTRIPMINE,
ANIM_CR_AIMTRIPMINE,
ANIM_CR_SHOOTTRIPMINE,
ANIM_AIM1HAND,
ANIM_SHOOT1HAND,
ANIM_CR_AIM1HAND,
ANIM_CR_SHOOT1HAND,
ANIM_AIMPYTHON,
ANIM_SHOOTPYTHON,
ANIM_CR_AIMPYTHON,
ANIM_CR_SHOOTPYTHON,
ANIM_AIMSHOTGUN,
ANIM_SHOOTSHOTGUN,
ANIM_CR_AIMSHOTGUN,
ANIM_CR_SHOOTSHOTGUN,
ANIM_AIMGAUSS,
ANIM_SHOOTGAUSS,
ANIM_CR_AIMGAUSS,
ANIM_CR_SHOOTGAUSS,
ANIM_AIMMP5,
ANIM_SHOOTMP5,
ANIM_CR_AIMMP5,
ANIM_CR_SHOOTMP5,
ANIM_AIMRPG,
ANIM_SHOOTRPG,
ANIM_CR_AIMRPG,
ANIM_CR_SHOOTRPG,
ANIM_AIMEGON,
ANIM_SHOOTEGON,
ANIM_CR_AIMEGON,
ANIM_CR_SHOOTEGON,
ANIM_AIMSQUEAK,
ANIM_SHOOTSQUEAK,
ANIM_CR_AIMSQUEAK,
ANIM_CR_SHOOTSQUEAK,
ANIM_AIMHIVE,
ANIM_SHOOTHIVE,
ANIM_CR_AIMHIVE,
ANIM_CR_SHOOTHIVE,
ANIM_AIMBOW,
ANIM_SHOOTBOW,
ANIM_CR_AIMBOW,
ANIM_CR_SHOOTBOW
};
void Animation_PlayerTop(player, float, float);
void Animation_PlayerBottom(player, float, float);

130
src/shared/animations.qc Normal file
View file

@ -0,0 +1,130 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
.float subblend2frac;
.float subblendfrac;
.float baseframe1time;
.float bonecontrol1;
.float bonecontrol2;
.float bonecontrol3;
.float bonecontrol4;
void Animation_Print(string sWow) {
#ifdef CLIENT
print(sprintf("[DEBUG] %s", sWow));
#else
bprint(PRINT_HIGH, sprintf("SSQC: %s", sWow) );
#endif
}
void
Animation_TimerUpdate(player pl, float ftime)
{
makevectors([0, pl.angles[1], 0]);
/* top animation is always just being incremented */
pl.anim_top_time += ftime;
pl.anim_top_delay -= ftime;
/* we may be walking backwards, thus decrement bottom */
if (dotproduct(pl.velocity, v_forward) < 0) {
pl.anim_bottom_time -= ftime;
} else {
pl.anim_bottom_time += ftime;
}
}
/*
=================
Animation_PlayerUpdate
Called every frame to update the animation sequences
depending on what the player is doing
=================
*/
void
Animation_PlayerUpdate(player pl)
{
pl.basebone = gettagindex(pl, "Bip01 Spine1");
if (pl.anim_top_delay <= 0.0f) {
pl.anim_top = Weapons_GetAim(pl.activeweapon);
}
if (vlen(pl.velocity) == 0) {
if (pl.flags & FL_CROUCHING) {
pl.anim_bottom = ANIM_CROUCHIDLE;
} else {
pl.anim_bottom = ANIM_IDLE;
}
} else if (vlen(pl.velocity) < 150) {
if (pl.flags & FL_CROUCHING) {
pl.anim_bottom = ANIM_CRAWL;
} else {
pl.anim_bottom = ANIM_WALK;
}
} else if (vlen(pl.velocity) > 150) {
if (pl.flags & FL_CROUCHING) {
pl.anim_bottom = ANIM_CRAWL;
} else {
pl.anim_bottom = ANIM_RUN;
}
}
pl.frame = pl.anim_top;
pl.frame1time = pl.anim_top_time;
pl.baseframe = pl.anim_bottom;
pl.baseframe1time = pl.anim_bottom_time;
/* hack, we can't play the animations in reverse the normal way */
if (pl.frame1time < 0.0f) {
pl.frame1time = 10.0f;
}
makevectors([0, pl.angles[1], 0]);
float fCorrect = dotproduct(pl.velocity, v_right) * 0.25f;
/* Turn torso */
pl.bonecontrol1 = fCorrect;
pl.bonecontrol2 = pl.bonecontrol1 * 0.5;
pl.bonecontrol3 = pl.bonecontrol2 * 0.5;
pl.bonecontrol4 = pl.bonecontrol3 * 0.5;
/* Correct the legs */
pl.angles[1] -= fCorrect;
#ifdef SERVER
pl.subblendfrac =
pl.subblend2frac = pl.v_angle[0] / 90;
#else
pl.subblendfrac =
pl.subblend2frac = pl.pitch / 90;
#endif
}
void
Animation_PlayerTop(player pl, float topanim, float timer)
{
pl.anim_top = topanim;
pl.anim_top_time = 0.0f;
pl.anim_top_delay = timer;
}
void
Animation_PlayerBottom(player pl, float botanim, float timer)
{
pl.anim_bottom = botanim;
}

135
src/shared/defs.h Normal file
View file

@ -0,0 +1,135 @@
enum TS_GameMode{
DEATHMATCH,
TEAM_DEATHMATCH,
TEAM_PLAY
};
enum TS_Team{
TEAM_NONE = -1,
TEAM_1 = 0,
TEAM_2 = 1,
TEAM_3 = 2,
TEAM_4 = 3
};
#ifdef SSQC
//server CVar
var float autocvar_weaponstay = 60; //default originally 15
#endif
// for convenience and clarity, see fteextensions.qc for the rest of the SOUNDFLAG choices
#define SOUNDFLAG_NONE 0
// Can use SOUNDFLAG_PLAYER_SHARED in most places meant to play sounds client & serverside.
// UNICAST stops the sound from playing for the client who called it since that client
// played it themselves already, after all they sent the message to the server to begin with.
// Some other flags too. To avoid UNICAST, use SOUNDFLAG_COMMON instead.
#ifdef CLIENT
#define SOUNDFLAG_PLAYER_SHARED (SOUNDFLAG_NOSPACIALISE | SOUNDFLAG_FOLLOW)
#define SOUNDFLAG_PLAYER_COMMON (SOUNDFLAG_NOSPACIALISE | SOUNDFLAG_FOLLOW)
#else //SERVER
#define SOUNDFLAG_PLAYER_SHARED (SOUNDFLAG_FOLLOW | SOUNDFLAG_UNICAST)
#define SOUNDFLAG_PLAYER_COMMON (SOUNDFLAG_FOLLOW)
#endif
// OLD WAY! Blah, forgot about this, just use the new names above.
// Pretty simple way to handle the server not having the "NOSPACIALISE" flag.
// Sounds that depend on being played very neatly clientside really just need to...
// be played clientside though (send an event from the server to play it there).
// Really SOUNDFLAG_CUSTOMCLIENT -> SOUNDFLAG_PLAYER_COMMON .
#ifdef CSQC
#define SOUNDFLAG_CUSTOMCLIENT SOUNDFLAG_FOLLOW | SOUNDFLAG_NOSPACIALISE
#else
#define SOUNDFLAG_CUSTOMCLIENT SOUNDFLAG_FOLLOW
#endif
// ???
//#pragma target FTE
//#pragma flag enable lo //enable short-circuiting
//TAGGG
//FLAG. Rename of the same value used by FL_SEMI_TOGGLED, since that name isn't used in The Specialists
//now. It will instead tell whether this is the first time firing since a release or not.
// One for secondary added too. hope that's a good idea
// ...unfortunately using these for flag values might've been a bad idea.
// Even in the old version, "1<<21" was already used for something else.
// They've been offset in the new version (base FreeCS by the way).
// So these will just be player bool's instead.
//#define FL_PRIMARY_FIRE_FIRST_FRAME (1<<20)
//#define FL_SECONDARY_FIRE_FIRST_FRAME (1<<21)
// MODERN FREECS STATS (in case they're needed for the sake of being
// an extensive gamemod, but seeing as FreeHL has none of these, I doubt
// that)
/*
STAT_MONEY = 34,
STAT_PROGRESS,
STAT_GAMETIME,
STAT_GAMESTATE
*/
// Why 34? See the fteextensions.qc (built-in / engine-provided things),
// looks like #33 is reserved. FreeCS still complies with this so trusting that.
#define STAT_BUILTIN_SEPARATOR 34
//TAGGG - Still need to remove some irrelevant ones like BUYZONE,, ESCAPEZONE, WON_T, etc.
// Also starting at stat 34? Feel some constant would be more comfortable for that, might not be an option though.
enum {
STAT_BUYZONE = STAT_BUILTIN_SEPARATOR,
STAT_MONEY,
STAT_FLAGS,
STAT_PROGRESS,
STAT_TEAM,
STAT_GAMETIME,
STAT_GAMESTATE,
/*
STAT_WON_T,
STAT_WON_CT,
*/
//TAGGG - NEW
//TAGGG - INCLUSION. just to show up in searches, new stuff though
STAT_RULE_MONEYALLOWED,
STAT_RULE_MAXWEIGHTSLOTS
};
enum {
GAME_INACTIVE,
GAME_COMMENCING,
GAME_FREEZE,
GAME_ACTIVE,
GAME_END,
GAME_OVER
};
var float autocvar_movemodmulti = 1;
//float Game_GetFriction(entity eTarget);
//float Game_GetMaxSpeed( entity eTarget );
// Prototype for weapons_common.qc method(s)!
// For things here to be aware of these earlier
void Weapons_Draw(void);
void Weapons_Holster(void);

20
src/shared/effects.h Normal file
View file

@ -0,0 +1,20 @@
#ifdef SSQC
void Effect_Explosion(vector vecOrigin );
#else
void EV_Effect_Explosion(vector vecOrigin );
#endif
#ifdef SSQC
//TAGGG - Thank you The Wastes!
void Effect_ScreenShake(vector vecOrigin, float fRadius, float fStrength );
#else
void EV_Effect_ScreenShake(int iType);
#endif
void Effect_CreateExplosion(vector vPos);

121
src/shared/effects.qc Normal file
View file

@ -0,0 +1,121 @@
//TAGGG - INCLUSION. and new file.
//Try copying from _base/shared/effects.c. From the up to date repository or the old one, dunno.
#ifdef SSQC
void Effect_Explosion(vector vecOrigin )
{
//the specialists explosion does not do this, strangely enough.
//Decals_PlaceScorch(vecOrigin);
WriteByte( MSG_MULTICAST, SVC_CGAMEPACKET );
WriteByte( MSG_MULTICAST, EVENT_TS::EFFECT_EXPLOSION );
WriteCoord( MSG_MULTICAST, vecOrigin[0] );
WriteCoord( MSG_MULTICAST, vecOrigin[1] );
WriteCoord( MSG_MULTICAST, vecOrigin[2] );
msg_entity = world;
multicast( vecOrigin, MULTICAST_PVS );
}
#else
void EV_Effect_Explosion(vector vecOrigin ){
pointparticles( PART_EXPLOSION, vecOrigin, [0,0,0], 1 );
}
#endif
#ifdef SSQC
//TAGGG - Thank you The Wastes!
void Effect_ScreenShake(vector vecOrigin, float fRadius, float fStrength )
{
entity eDChain = findradius( vecOrigin, fRadius );
while( eDChain ) {
if ( eDChain.classname == "player" ) {
float fDiff = vlen( vecOrigin - eDChain.origin );
fDiff = ( fRadius - fDiff ) / fRadius;
fStrength = fStrength * fDiff;
if ( fDiff > 0 ) {
WriteByte( MSG_MULTICAST, SVC_CGAMEPACKET );
WriteByte( MSG_MULTICAST, EVENT_TS::EFFECT_SHAKE );
WriteByte( MSG_MULTICAST, (int)fStrength );
msg_entity = eDChain;
multicast( [0,0,0], MULTICAST_ONE );
}
}
eDChain = eDChain.chain;
}
}
#else
//void EV_Effect_ScreenShake(vector vecOrigin, float fRadius, float fStrength ){
// no, works a bit differently.
void EV_Effect_ScreenShake(int iType){
View_ShakeCreate( iType );
}
#endif
//TAGGG - for the specialists, redirecting this to some other logic.
void
Effect_CreateExplosion(vector vPos)
{
#ifdef TS
#ifdef SSQC
Effect_ScreenShake( vPos, 2048, 255 );
Effect_Explosion( vPos + [0,0,16] );
#else
printfline("WARNING!!! Effect_CreateExplosion called clientside, but not supposed to be!");
#endif
#else
/*
#ifdef SERVER
WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET);
WriteByte(MSG_MULTICAST, EV_EXPLOSION);
WriteCoord(MSG_MULTICAST, vecPos[0]);
WriteCoord(MSG_MULTICAST, vecPos[1]);
WriteCoord(MSG_MULTICAST, vecPos[2]);
msg_entity = self;
multicast(vecPos, MULTICAST_PVS);
#else
Decals_Place(vecPos, sprintf("{scorch%d", floor(random(1,4))));
vecPos[2] += 48;
env_sprite eExplosion = spawn(env_sprite);
setorigin(eExplosion, vecPos);
setmodel(eExplosion, "sprites/fexplo.spr");
sound(eExplosion, CHAN_WEAPON, sprintf("weapons/explode%d.wav", floor(random() * 3) + 3), 1, ATTN_NORM);
//eExplosion.think = FX_Explosion_Animate;
eExplosion.effects = EF_ADDITIVE;
eExplosion.drawmask = MASK_ENGINE;
eExplosion.maxframe = modelframecount(eExplosion.modelindex);
eExplosion.loops = 0;
eExplosion.framerate = 20;
eExplosion.nextthink = time + 0.05f;
pointparticles(FX_EXPLOSION_MAIN, vecPos, [0,0,0], 1);
pointparticles(FX_EXPLOSION_BS, vecPos, [0,0,0], 1);
#endif
*/
#endif
}

25
src/shared/entities.h Normal file
View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//TAGGG - Definitely change this!!!
// Do we need to use this for powerups? anything else new?
enum
{
ENT_POWERUP = ENT_SEPARATOR
// ...
// etc.
};

109
src/shared/event_custom.h Normal file
View file

@ -0,0 +1,109 @@
BOOLEAN TS_Weapon_PrimaryAttackRelease(player localPlayer, BOOLEAN hasAmmo);
BOOLEAN TS_Weapon_SecondaryAttackRelease(player localPlayer, BOOLEAN hasAmmo);
void TS_Weapon_Draw_extra(void);
void TS_playerEquippedWeapon_Shared(player localPlayer, int newWeaponEquipped, BOOLEAN useAkimbo);
#ifdef CSQC
void _TS_playerEquippedWeapon(player localPlayer, int newWeaponEquipped, BOOLEAN useAkimbo);
void TS_playerEquippedWeapon(player localPlayer, int newWeaponEquipped, BOOLEAN useAkimbo);
#endif
#ifdef SSQC
int _TS_playerEquippedWeapon(player localPlayer, int newWeaponEquipped, BOOLEAN useAkimbo);
void TS_playerEquippedWeapon(player localPlayer, int newWeaponEquipped, BOOLEAN useAkimbo);
void CSEv_TS_playerEquippedWeapon_ii(int newWeaponEquipped, BOOLEAN useAkimbo);
#endif
#ifdef SSQC
void TS_PlayInsertShellSound(player localPlayer);
#else
void TS_PlayInsertShellSound(player localPlayer);
void EV_TS_PlayInsertShellSound(player localPlayer);
#endif
#ifdef SSQC
void TS_resetViewModel(player localPlayer);
#else
void TS_resetViewModel(player localPlayer);
void EV_TS_resetViewModel(void);
#endif
#ifdef SSQC
void TS_resetPlayer(player localPlayer, BOOLEAN resetInventory)
#else
void EV_TS_resetPlayer(player localPlayer, BOOLEAN resetInventory)
#endif
#ifdef CSQC
//NOTICE - clientside components removed, except for clientside being able
// to signal that the server should remove a weapon.
// This needs no clientside logic equivalent to be mirrored.
/*
void _TS_playerDropWeapon(player localPlayer);
*/
void TS_playerDropWeapon(void);
/*
void EV_TS_playerDropWeapon(player localPlayer);
*/
#endif
#ifdef SSQC
//that's it really serverside.
void _TS_playerDropWeapon(void);
void TS_playerDropWeapon(void);
void CSEv_TS_playerDropWeapon_(void);
#endif
#ifdef SERVER
void CSEv_TS_playerChangeFiremode_(void);
#endif
void TS_playerChangeFiremode(void);
void _TS_playerChangeFiremode(void )
#ifdef SERVER
void CSEv_TS_playerUseItems_(void);
#endif
void TS_playerUseItems(void);
void _TS_playerUseItems(void);
#ifdef SERVER
void CSEv_TS_playerUsePowerup_(void);
#endif
void TS_playerUsePowerup(void);
void _TS_playerUsePowerup(void);
#ifdef SERVER
void CSEv_TS_playerCallAlt1_(void);
#endif
void TS_playerCallAlt1(void);
void _TS_playerCallAlt1(void);
#ifdef SERVER
void CSEv_TS_playerCallAlt2_(void);
#endif
void TS_playerCallAlt2(void);
void _TS_playerCallAlt2(void);
void playerEquipIdeal(player localPlayer);
void playerEquipIdealSafe(player localPlayer);

973
src/shared/event_custom.qc Normal file
View file

@ -0,0 +1,973 @@
BOOLEAN TS_Weapon_PrimaryAttackRelease(player localPlayer, BOOLEAN hasAmmo){
// WARNING! This comment is really out of date, fWeaponEventPlayer is no longer a thing
// ------------------------------
// what are "fWeaponEventPlayer" and "player_localentnum"?
// we need to figure this shizz out
// "fWeaponEventPlayer" is in "ts/client/defs.h". its assigned a lot in "ts/client/event.c".
// at least "player_localentnum" is part of the builtins.
player pl = localPlayer;
if(pl.inventoryEquippedIndex == -1){
return FALSE;
}
weapondynamic_t dynaRef = pl.ary_myWeapons[pl.inventoryEquippedIndex];
weapondata_basic_t* basicP = pl.getEquippedWeaponData();
return (*basicP).vOnPrimaryAttackRelease(pl, dynaRef, hasAmmo);
}
BOOLEAN TS_Weapon_SecondaryAttackRelease(player localPlayer, BOOLEAN hasAmmo){
player pl = localPlayer;
if(pl.inventoryEquippedIndex == -1){
return FALSE;
}
weapondynamic_t dynaRef = pl.ary_myWeapons[pl.inventoryEquippedIndex];
weapondata_basic_t* basicP = pl.getEquippedWeaponData();
return (*basicP).vOnSecondaryAttackRelease(pl, dynaRef, hasAmmo);
}//END OF TS_Weapon_SecondaryAttackRelease
// For client and serverside to call alongside a "Weapons_Draw" call,
// even if Weapons_Draw itself is skipped in the clientside weapon-select route.
// This does get called by ts/client/view.qc calling Weapons_Draw for clientside though,
// so treat this as 'whatever I want to happen on drawing a weapon'.
void TS_Weapon_Draw_extra(void){
player pl = (player)self;
weapondynamic_t dynaRef;
printfline("PLAYER DREW WEAPON: invID:%i (g_weapons ID:%d)", pl.inventoryEquippedIndex, pl.activeweapon);
dynaRef = pl.ary_myWeapons[pl.inventoryEquippedIndex];
pl.currentZoomChoice = -1;
pl.aryNextBurstShotTime_softLength = 0;
pl.aryNextBurstShotTime_listenIndex = -1;
// Cheap, but should work? Some things above may be redundant with this
pl.reset(FALSE);
}
//CLONED FROM weapon.h
void TS_Weapon_Draw(player localPlayer, int weaponEquipped, BOOLEAN useAkimbo ) {
player pl = localPlayer;
//weapondynamic_t dynaRef;
//weapondynamic_t dynaRefPRE;
// Anything equipped already? Call vOnUnEquip of that weapon first
if(pl.inventoryEquippedIndex != -1){
/*
dynaRefPRE = pl.ary_myWeapons[pl.inventoryEquippedIndex];
weapondata_basic_t* basicP = pl.getEquippedWeaponData();
(*basicP).vOnUnEquip(pl, dynaRefPRE);
*/
// How about Nuclide's 'Holster' instead?
Weapons_Holster();
pl.equippedWeaponDeleteCheck();
}
pl.setInventoryEquippedIndex_Akimbo(weaponEquipped, useAkimbo);
// actually no, I guess we just let rendering pick up on this to know when to call draw
// MMMMMmmmmmm sounds odd if Draw containts non-rendering-related startup script,
// rather this happen as son as possible for now.
#ifdef CSQC
printfline("TS_Weapon_Draw (client direct call): prev:%i curr:%d", pSeat->m_iLastWeapon, pl.activeweapon);
#endif
// This Weapons_Draw call is only for serverside, at clientside, we trust
// <gamemod>/src/client/view.qc to notice the weapon change and call Weapons_Draw for us.
// This is more accurate to the way seen in other Nuclide gamemods.
// If Holster animations were supported (anim to play on switching weapons that must
// finish before a switch happens, looks like putting the weapon away out of view
// first), some delay until calling Weapons_Draw here or in ts/client/view.qc to let
// that holster anim finish playing would be a good idea.
// TEST! Force the call now anyway clientside too.
//#ifdef SSQC
TS_Weapon_Draw_extra();
Weapons_Draw();
//#else
// // so that any choice of weapon, same as before or even nothing, will still
// // let client/view.qc do the whole viewmodel routine again
// // This might not even be effective here for spawns, see EVENT_TS::SPAWN
// pSeat->m_iLastWeapon = -2;
//#endif
// Is that necessary?
#ifdef CLIENT
HUD_CloseWeaponSelect(TRUE);
#endif
}//TS_Weapon_Draw
//This will be serverside only. Clientside should never call this.
//TAGGG - TODO. Would making this shared be OK? Seems to have worked for grenades and throwing knives
// on removing themselves on running out of ammo.
// Just remember that spawning the actual entitiy won't be happening clientside in any form.
#ifdef SSQC
void TS_Weapon_Drop() {
player pl = (player)self;
if(pl.inventoryEquippedIndex == -1){
//stop. nothing to drop.. ?
return;
}
// in case vOnUnEquip alters the weapon equipped, like a grenade switching out.
int currentWeaponMem = pl.inventoryEquippedIndex;
//TAGGG - like equipping a new weapon, we have to let the existing equipped
// weapon know that it's about to be unequipped.
if(currentWeaponMem != -1){
pl.dropWeapon(currentWeaponMem, FALSE);
//}//END OF pl.inventoryEquippedIndex check
}
////////////////////////////////////
}//TS_Weapon_Drop
#endif
//TAGGG - CRITICAL! TS_playerEquippedWeapon methods dummied out to stop conflicts with
// the new Nuclide way
void TS_playerEquippedWeapon_Shared(player localPlayer, int newWeaponEquipped, BOOLEAN useAkimbo){
/*
if(localPlayer.inventoryEquippedIndex == newWeaponEquipped && useAkimbo == localPlayer.weaponEquippedAkimbo ){
// equipping the same weapon? Ignore this call then!
return;
}
*/
_TS_playerEquippedWeapon(localPlayer, newWeaponEquipped, useAkimbo);
}
#ifdef CSQC
// Actual clientside logic related to this action. Need to change the viewmodel
// on a change like this.
// Note that viewmodels check for "pl.inventoryEquippedIndex" being changed in ts/client/view.c.
// This is OK. pl.inventoryEquippedIndex is updated from server to client in client & server/player.c, read/sendEntity methods. So everything works out.
void _TS_playerEquippedWeapon(player localPlayer, int newWeaponEquipped, BOOLEAN useAkimbo){
//return;
player pl = localPlayer;
TS_Weapon_Draw(pl, newWeaponEquipped, useAkimbo);
}
// The client set the equippedWeapon, and needs to tell the server to keep it in synch
// NOTICE - if we commit the change this very moment, it actually messes with prediction stuff.
// So don't do any real client changes from calls when the client starts them.
// The client will tell the server to do something, and THEN the server relays that to the client.
// Whatever just go with it.
void TS_playerEquippedWeapon(player localPlayer, int newWeaponEquipped, BOOLEAN useAkimbo){
//pSeat->ePlayer = self = findfloat(world, entnum, player_localentnum);
//printfline("WELL??! %i %i %d %d", localPlayer.inventoryEquippedIndex, newWeaponEquipped, useAkimbo, localPlayer.weaponEquippedAkimbo);
//TODO - CRITICAL! Although - might not be that bad.
// Note that any weapon removal (only removing the currently equipped weapon is possible) also
// sets the current equipped weapon to -1. So any other choice of weapon after this has to work.
// ----------OLD COMMENT--------
// Check for having recently deleted a weapon. If so, forget this check. The choice picked
// is guaranteed to be ok.
// Say there are 3 weapons, #0, #1, #2, and #3. Say #2 is equipped now.
// If #2 is deleted (shifts #3 to be the new #2), and #2 is picked to be the new weapon,
// that would cause this check to deny picking the 'new' weapon taking up slot #2.
if(localPlayer.inventoryEquippedIndex == newWeaponEquipped && useAkimbo == localPlayer.weaponEquippedAkimbo ){
// equipping the same weapon? Ignore this call then!
return;
}
_TS_playerEquippedWeapon(localPlayer, newWeaponEquipped, useAkimbo);
sendevent("TS_playerEquippedWeapon", "ii", newWeaponEquipped, (int)useAkimbo);
}
#endif
#ifdef SSQC
//that's it really serverside.
int _TS_playerEquippedWeapon(player localPlayer, int newWeaponEquipped, BOOLEAN useAkimbo){
player pl = localPlayer;
TS_Weapon_Draw(pl, newWeaponEquipped, useAkimbo);
return newWeaponEquipped; //in case any changes were made to this, let the caller know
}
//The server set the equippedWeapon, and needs to tell the client to keep it in synch
void TS_playerEquippedWeapon(player localPlayer, int newWeaponEquipped, BOOLEAN useAkimbo){
_TS_playerEquippedWeapon(localPlayer, newWeaponEquipped, useAkimbo);
// force a sendoff soon!
localPlayer.activeweapon_net = 255;
localPlayer.inventoryEquippedIndex_net = 255;
}
//This is a received call from the client.
//We do need to let the server update first, which then tells the client
//of the updated state. Even though the client initiated the call in this case.
//The server can initiate the call too.
//Either side can call "TS_playerEquippedWeapon" to get the orders right.
void CSEv_TS_playerEquippedWeapon_ii(int newWeaponEquipped, BOOLEAN useAkimbo){
player pl = (player)self;
TS_playerEquippedWeapon(pl, newWeaponEquipped, useAkimbo);
}
#endif
#ifdef SSQC
//SERVER ONLY. Send a request to change the animation of the viewmodel directly.
void TS_PlayInsertShellSound(player localPlayer){
/*
WriteByte( MSG_MULTICAST, SVC_CGAMEPACKET );
WriteByte( MSG_MULTICAST, EVENT_TS::PLAY_INSERT_SHELL_SND );
msg_entity = localPlayer;
multicast( [0,0,0], MULTICAST_ONE );
*/
}
#else
void TS_PlayInsertShellSound(player localPlayer){
EV_TS_PlayInsertShellSound(localPlayer);
}
//CLIENTSIDE. now what do I do over here?
void EV_TS_PlayInsertShellSound(player localPlayer){
//TODO - lower attenuation maybe?
// Nevermind, this is a client-only sound for now anyway.
localsound("weapons/insert-shell.wav", CHAN_AUTO, 1.0f);
}
#endif
// The server may want to tell the client to reset its viewmodel.
// DUMMIED - nevermind that for now, assuming the logic is called from server/client
// individually like a lot of weapon's logic.
#ifdef SSQC
void TS_resetViewModel(player localPlayer){
/*
//localPlayer.lasersightUnlockTime = FALSE; //reset
WriteByte( MSG_MULTICAST, SVC_CGAMEPACKET );
WriteByte( MSG_MULTICAST, EVENT_TS::RESET_VIEW_MODEL );
msg_entity = localPlayer;
multicast( [0,0,0], MULTICAST_ONE );
*/
}
#else
//CLIENT
void TS_resetViewModel(player localPlayer){
localPlayer.lasersightUnlockTime = FALSE; //reset
EV_TS_resetViewModel();
}
void EV_TS_resetViewModel(void){
pSeat->m_iLastWeapon = -1; //any weapon slot choice will refresh the view model.
resetViewModel();
}
#endif
// Want to reset the player between spawns?
#ifdef SSQC
void TS_resetPlayer(player localPlayer, BOOLEAN resetInventory){
if(localPlayer == NULL){
return;
}
if(localPlayer.classname != "player"){
return;
}
//reset our inventory, serverside.
localPlayer.reset(resetInventory);
//And tell the client to do the same.
WriteByte( MSG_MULTICAST, SVC_CGAMEPACKET );
WriteByte( MSG_MULTICAST, EVENT_TS::RESET_PLAYER );
WriteByte( MSG_MULTICAST, resetInventory );
msg_entity = localPlayer;
multicast( [0,0,0], MULTICAST_ONE );
}
#else
//CLIENT
void EV_TS_resetPlayer(player localPlayer, BOOLEAN resetInventory){
if(localPlayer == NULL){
return;
}
// TODO: just have this check in receiving events to begin with?
if(localPlayer.classname == "player"){
localPlayer.reset(resetInventory);
}else{
// ????????????
}
}
#endif
#ifdef CSQC
//NOTICE - clientside components removed, except for clientside being able
// to signal that the server should remove a weapon.
// This needs no clientside logic equivalent to be mirrored.
/*
void _TS_playerDropWeapon(player localPlayer){
player pl = localPlayer;
TS_Weapon_Drop();
}
*/
//TAGGG - TODO. Do anything clientside here too, maybe? Unsure.
void TS_playerDropWeapon(void){
sendevent("TS_playerDropWeapon", "");
}//END OF TS_playerDropWeapon
/*
//This is a received call from the server - no need to update it back
//NOTICE - this is the callback telling us to do something. Yes do something.
void EV_TS_playerDropWeapon(player localPlayer){
player pl = localPlayer;
//pSeat->ePlayer = self = findfloat(world, entnum, player_localentnum);
_TS_playerDropWeapon(localPlayer);
}
*/
#endif
#ifdef SSQC
//that's it really serverside.
void _TS_playerDropWeapon(void){
player pl = (player)self;
TS_Weapon_Drop();
}//_TS_playerDropWeapon
//The server set the equippedWeapon, and needs to tell the client to keep it in synch
void TS_playerDropWeapon(void){
player pl = (player)self;
//pSeat->ePlayer = self = findfloat(world, entnum, player_localentnum);
_TS_playerDropWeapon();
WriteByte( MSG_MULTICAST, SVC_CGAMEPACKET );
WriteByte( MSG_MULTICAST, EVENT_TS::DROP_WEAPON );
msg_entity = pl;
multicast( [0,0,0], MULTICAST_ONE );
}
//This is a received call from the client.
//We do need to let the server update first, which then tells the client
//of the updated state. Even though the client initiated the call in this case.
//The server can initiate the call too.
//Either side can call "TS_playerEquippedWeapon" to get the orders right.
void CSEv_TS_playerDropWeapon_(){
player pl = (player)self;
TS_playerDropWeapon();
}
#endif
#ifdef SERVER
// Server receives the message
void CSEv_TS_playerChangeFiremode_(void){
_TS_playerChangeFiremode();
}
#endif
// For now, only the client will expect direct calls to these.
// Client sends the input, server receives a message in response to perform things on its end too.
void TS_playerChangeFiremode(void){
player pl = (player)self;
if(pl.classname == "spectator" || pl.inventoryEquippedIndex == -1){
//well gee that is no good
return;
}
#ifdef CLIENT
sendevent("TS_playerChangeFiremode", "");
// and do it my side too?
_TS_playerChangeFiremode();
#else
// SHOULD NOT HAPPEN, should call the CSEv_ version instead
#endif
}
void _TS_playerChangeFiremode(void ) {
player pl = (player)self;
weapondynamic_t dynaRef = pl.ary_myWeapons[pl.inventoryEquippedIndex];
if(dynaRef.weaponTypeID == WEAPONDATA_TYPEID_GUN || dynaRef.weaponTypeID == WEAPONDATA_TYPEID_IRONSIGHT){
weapondata_gun_t* basicPointer = (weapondata_gun_t*) pl.getEquippedWeaponData();
weapondata_gun_t basicRef = *(basicPointer);
// If the bitmask for available firemodes for this weapon is just a flat "0" (none specified?)
// OR our bitmask matches the current firemode exactly (only one bit present), there is no point
// in trying to switch firemodes.
if(basicRef.iBitsFireModes == 0){
return;
}
int* fireModeVar;
if(!pl.weaponEquippedAkimbo){
fireModeVar = &dynaRef.iFireMode;
}else{
fireModeVar = &dynaRef.iFireModeAkimbo;
}
if( (*fireModeVar) == basicRef.iBitsFireModes){
return;
}
//push the bits up until we get a match
int originalChoice;
int currentChoice;
//little bit of filtering for safety
if((*fireModeVar) <= 0 || (*fireModeVar) > 64){
//Bad firemode value? Force the current to 1.
//The original is 64, to just stop at the greatest bit possible
//if nothing else is reached.
originalChoice = 64;
currentChoice = 1;
}else{
//safe!
originalChoice = (*fireModeVar);
currentChoice = originalChoice << 1;
}
while(currentChoice != originalChoice){
if(currentChoice >= 64){ // (1 << 6)
currentChoice = 1; //reset from the other way.
//This checks the bits starting from the beginning too.
}
if(basicRef.iBitsFireModes & currentChoice){
//this power of 2 is a valid fireMode? pick it
(*fireModeVar) = currentChoice;
return;
}
currentChoice = currentChoice << 1;
}
//couldn't change? only fire mode... hopefully anyways.
}//END OF "is a gun" - check
}//_TS_playerChangeFiremode
#ifdef SERVER
// Server receives the message
void CSEv_TS_playerUseItems_(void){
_TS_playerUseItems();
}
#endif
// For now, only the client will expect direct calls to these.
// Client sends the input, server receives a message in response to perform things on its end too.
void TS_playerUseItems(void){
player pl = (player)self;
if(pl.classname == "spectator" || pl.inventoryEquippedIndex == -1){
//well gee that is no good
return;
}
#ifdef CLIENT
sendevent("TS_playerUseItems", "");
// and do it my side too?
_TS_playerUseItems();
#else
// SHOULD NOT HAPPEN, should call the CSEv_ version instead
#endif
}
// For making the lasersight & flashlights toggle on/off with the "n" key by default. Cycle through the combos of
// off/on.
void _TS_playerUseItems(void){
player pl = (player)self;
if(pl.inventoryEquippedIndex != -1){
weapondynamic_t dynaRef = pl.ary_myWeapons[pl.inventoryEquippedIndex];
if(dynaRef.weaponTypeID == WEAPONDATA_TYPEID_GUN || dynaRef.weaponTypeID == WEAPONDATA_TYPEID_IRONSIGHT){
weapondata_basic_t* basicP = pl.getEquippedWeaponData();
int legalBuyOpts = (dynaRef.iBitsUpgrade & (*basicP).iBitsUpgrade) & (BITMASK_WEAPONOPT_TOGGLEABLE);
// We must have the flashlight bit on, AND support it on our weapon, AND it be a possibility according to weapon data.
int legalBuyOpts_on = (dynaRef.iBitsUpgrade_on & legalBuyOpts);
int bitCount = count1Bits(legalBuyOpts, BITMASK_WEAPONOPT_TOGGLEABLE_MIN, BITMASK_WEAPONOPT_TOGGLEABLE_MAX);
int bitCount_on = count1Bits(legalBuyOpts_on, BITMASK_WEAPONOPT_TOGGLEABLE_MIN, BITMASK_WEAPONOPT_TOGGLEABLE_MAX);
int firstBit;
int firstBit_on;
if(bitCount == 0){
//no togglable buyopts at all? I guess that's it.
}else{
if(bitCount == 1){
//one bit available? just toggle on/off then.
if(bitCount_on){
dynaRef.iBitsUpgrade_on = 0;
}else{
dynaRef.iBitsUpgrade_on = legalBuyOpts;
}
}else{
//variable number of bits? See how many bits are on right now.
if(bitCount_on == 0){
// No bits on?
// Turn the first one on.
firstBit = findFirst1Bit(legalBuyOpts, BITMASK_WEAPONOPT_TOGGLEABLE_MIN, BITMASK_WEAPONOPT_TOGGLEABLE_MAX);
dynaRef.iBitsUpgrade_on |= firstBit;
}else if(bitCount_on == 1){
// If we're not the last bit, turn the next one on.
// Start by looking at the next bit (bit << 1)
firstBit_on = findFirst1Bit(legalBuyOpts, legalBuyOpts_on << 1, BITMASK_WEAPONOPT_TOGGLEABLE_MAX);
if(firstBit_on != 0){
//we'll take it!
dynaRef.iBitsUpgrade_on = firstBit_on;
}else{
//ran out of available bits? Take them all.
dynaRef.iBitsUpgrade_on = legalBuyOpts;
}
}else{
//more than 1? Turn them all off then.
dynaRef.iBitsUpgrade_on = 0;
}
}
// Let's be a clientside sound only.
#ifdef CLIENT
localsound("weapons/switch.wav", CHAN_AUTO, 1.0f);
#endif
// (METHOD MADE SINCE: TS_Weapons_PlaySoundChannelDirect)
// Any bits? Play this sound, some change happened
//sound(pl, CHAN_ITEM, "weapons/switch.wav", 1, ATTN_NONE, 100.0f, SOUNDFLAG_PLAYER_SHARED, 0);
/*
//TAGGG
// unicast demo . Why does the sound play clientside the first few times anyway?
// It shouldn't ever, with the clientside-call here disabled.
#ifdef CLIENT
//sound(pl, CHAN_ITEM, "weapons/switch.wav", 1, ATTN_NONE, 100.0f, SOUNDFLAG_NONE, 0);
#else
sound(pl, CHAN_ITEM, "weapons/switch.wav", 1, ATTN_NONE, 100.0f, SOUNDFLAG_UNICAST, 0);
#endif
*/
}//END OF bitcount checks
}///END OF _GUN or _IRONSIGHT type checks
}//END OF weaponEquippedID check
printfline("CSEv_TS_playerUseItems_");
}
#ifdef SERVER
// Server receives the message
void CSEv_TS_playerUsePowerup_(void){
_TS_playerUsePowerup();
}
#endif
// For now, only the client will expect direct calls to these.
// Client sends the input, server receives a message in response to perform things on its end too.
void TS_playerUsePowerup(void){
player pl = (player)self;
if(pl.classname == "spectator" || pl.inventoryEquippedIndex == -1){
//well gee that is no good
return;
}
#ifdef CLIENT
sendevent("TS_playerUsePowerup", "");
// and do it my side too?
_TS_playerUsePowerup();
#else
// SHOULD NOT HAPPEN, should call the CSEv_ version instead
#endif
}
//TAGGG - TODO.
// Applies the currently equipped player powerup if there is one.
void _TS_playerUsePowerup(void){
player pl = (player)self;
// ...
printfline("CSEv_TS_playerUsePowerup_");
}
#ifdef SERVER
// Server receives the message
void CSEv_TS_playerCallAlt1_(void){
_TS_playerCallAlt1();
}
#endif
// For now, only the client will expect direct calls to these.
// Client sends the input, server receives a message in response to perform things on its end too.
void TS_playerCallAlt1(void){
player pl = (player)self;
if(pl.classname == "spectator" || pl.inventoryEquippedIndex == -1){
//well gee that is no good
return;
}
#ifdef CLIENT
sendevent("TS_playerCallAlt1", "");
// and do it my side too?
_TS_playerCallAlt1();
#else
// SHOULD NOT HAPPEN, should call the CSEv_ version instead
#endif
}
// middle mouse wheel? Use for some stunts, at least ones that don't necessarily involve being mid-air.
// double-tapping space does that. I think?
void _TS_playerCallAlt1( void ) {
player pl = (player)self;
// ...
printfline("CSEv_TS_playerCallAlt1_");
}
#ifdef SERVER
// Server receives the message
void CSEv_TS_playerCallAlt2_(void){
_TS_playerCallAlt2();
}
#endif
// For now, only the client will expect direct calls to these.
// Client sends the input, server receives a message in response to perform things on its end too.
void TS_playerCallAlt2(void){
player pl = (player)self;
if(pl.classname == "spectator" || pl.inventoryEquippedIndex == -1){
//well gee that is no good
return;
}
#ifdef CLIENT
sendevent("TS_playerCallAlt2", "");
// and do it my side too?
_TS_playerCallAlt2();
#else
// SHOULD NOT HAPPEN, should call the CSEv_ version instead
#endif
}
// coldcock / weapon-melee?
void _TS_playerCallAlt2(void){
player pl = (player)self;
printfline("CSEv_TS_playerCallAlt2_");
weapondynamic_t dynaRef = pl.ary_myWeapons[pl.inventoryEquippedIndex];
weapondata_basic_t* basicP = pl.getEquippedWeaponData();
if(basicP != NULL){
(*basicP).vOnColdCock(pl, dynaRef);
}else{
// what. how.
printfline("WHAT. HOW.");
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//!!!! SERVER EVENTS ONLY BELOW HERE
#ifdef SSQC
///////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////
// !!! DEBUG STUFF?
void CSEv_TS_Debug_getOrigin_(void ) {
player pl = (player)self;
if(pl != NULL){
printfline("Client origin: %.1f %.1f %.1f", pl.origin.x, pl.origin.y, pl.origin.z);
}else{
printfline("ERROR: client null?");
}
}
void CSEv_TS_Debug_getAngle_(void ) {
player pl = (player)self;
if(pl != NULL){
printfline("Client angles : %.1f %.1f %.1f", pl.angles.x, pl.angles.y, pl.angles.z);
printfline("Client v_angle: %.1f %.1f %.1f", pl.v_angle.x, pl.v_angle.y, pl.v_angle.z);
}else{
printfline("ERROR: client null?");
}
}
//NOTICE - these event methods are server-side only.
// anything in the "#ifdef S..." something. I dunno.
// The client sends a variety of messages at one time to the server to see if the player can really afford what they are
// requesting. Secure... probably.
//TODO - PROTOTYPE THIS OR SET IT UP. whatever.
// Buys ammo of a certain type (ID), and ammount.
// Note that trying to exceed an ammo's max allowed ammount still caps at the ammmo's capacity.
// And if the player can't afford the ammount, the next greatest ammount to use the remaining money
// will be used instead.
void CSEv_PlayerBuyAmmo_TS_i( int iAmmoTypeID, int iAmmoAmount ) {
player pl = (player)self;
//Using this new var for safety.
int iActualAmmoAmount = iAmmoAmount;
if(iAmmoTypeID != -1){
ammodata_t ammoRef = *ary_ammoData[iAmmoTypeID];
//iActualAmmoAmount must be between 0 and ammoRef.iMax, inclusive on both. If the player had 0 ammo to begin with.
if(pl.ary_ammoTotal[iAmmoTypeID] + iActualAmmoAmount > ammoRef.iMax){
iActualAmmoAmount = ammoRef.iMax - pl.ary_ammoTotal[iAmmoTypeID];
}
if(iActualAmmoAmount <= 0){
//iActualAmmoAmount = 0;
//just stop, we aren't buying anything.
return;
}
int ammoCost = (int)ceil(iActualAmmoAmount * ammoRef.fPricePerBullet);
//11 / 16
//want to buy 5.
//but you have 36 dollars
//each ammo costs 10 dollars.
//
//50 dollers...
// 36 >= 50. nope.
//bulletsAfforded = floor(36 / 10)
//bulletsAfforded = 3
//11 + 3 is 14.
//iActualAmmoAmount = 3 .
//what if...
//11 + 8 is 19.
//11 + 8 = 19, > 16.
//so...
//iActualAmmoAmount = 16 - 11 (5)
if(!bRule_MoneyAllowed || pl.money >= ammoCost){
//afforded all of it. N ochange in iActualAmmoAmount.
}else{
//see what we can actually afford.
int bulletsAfforded = floor(pl.money / ammoRef.fPricePerBullet);
if(pl.ary_ammoTotal[iAmmoTypeID] + bulletsAfforded > ammoRef.iMax){
//Even if we could afford more, we can't buy any more than the max allowed.
//...not that this should be possible in this case.
iActualAmmoAmount = ammoRef.iMax - pl.ary_ammoTotal[iAmmoTypeID];
}else{
//If buying this many bullets does not put us over the limit, go ahead.
iActualAmmoAmount = bulletsAfforded;
}
//and so this is the new cost.
ammoCost = iActualAmmoAmount * ammoRef.fPricePerBullet;
}
//At this point we are buying "iActualAmmoAmount".
if(bRule_MoneyAllowed){
pl.money -= ammoCost;
}
pl.ary_ammoTotal[iAmmoTypeID] += iActualAmmoAmount;
}//END OF iAmmoTypeID valid check
}//END OF CSEv_PlayerBuyAmmo_TS_i
//This is used to signify that we're done sending weapon config over and want to pick a weapon to start equipped with.
//Order appears to be the highest slot number first, unless there is a weapon in slot 3 (small machine guns).
//Then it gets equipped instead.
void CSEv_PlayerBuyWeapon_TS_end_( void ) {
player pl = (player)self;
printfline("CSEv_PlayerBuyWeapon_TS_end_");
playerEquipIdeal(pl);
}
//Support upgrades, akimbo, etc. Include ammo if necessary!
//ACTUALLY... ammo will be signaled for purchase separately. The client still determines how much ammo to buy though.
void CSEv_PlayerBuyWeapon_TS_ii( int iWeaponTypeID, int iBuyOpts ) {
player pl = (player)self;
printf("Buying weaponTypeID:%i\n", iWeaponTypeID);
attemptBuyWeapon(pl, iWeaponTypeID, iBuyOpts, 1);
}
// Version that takes a count of the weapon, since throwables stack in place.
void CSEv_PlayerBuyWeaponThw_TS_iii( int iWeaponTypeID, int iBuyOpts, int iCount ) {
player pl = (player)self;
printf("Buying weaponTypeID (Thw):%i COUNT? %i\n", iWeaponTypeID, iCount);
//printfline("HOW MUCH MONEY WE GOT %.2f invCount:%i", pl.money, pl.ary_myWeapons_softMax);
//if ( Rules_BuyingPossible() == FALSE ) {
// return;
//}
//attemptAddWeaponToConfig(pl, iWeaponTypeID, iBuyOpts);
attemptBuyWeapon(pl, iWeaponTypeID, iBuyOpts, iCount);
}
#endif// SERVER
//Goes through the available weapons and picks what would make sense to equip.
//TAGGG TODO - SUGGESTION. Don't equip a weapon automatically if it's out of ammo,
// assuming we have an option that isn't out of ammo.
//TAGGG TODO MAJOR - check to see what place was previously selected, and then look to see where we can grab a new weapon if that changes things.
//That is, we have a few cases with special circumstances.
//If a singular version of an akimbo weapon was dropped, equip it's now singular version (1 of the two weapons left in inventory).
//I think that's it actually.
//nah, just check for that special case when dropping a weapon
void playerEquipIdeal(player localPlayer){
//go through all the players weapons. whichever has the highest slot number, except for 5, gets picked
player pl = localPlayer;
int bestSlotYet = 0;
weapondynamic_t weapon_DynaRef = NULL;
int weaponDynamicID_toEquip = -1;
//printfline("playerEquipIdeal: invcount: %i", pl.ary_myWeapons_softMax);
//work backwards, so the last item in a slot satisfies us
for(int i = pl.ary_myWeapons_softMax-1; i >= 0; i--){
//weapon_configRef = (weaponconfig_weapon_t) pl.weaponconfig_temp.ary_myWeapons[i];
weapon_DynaRef = (weapondynamic_t) pl.ary_myWeapons[i];
weapondata_basic_t* basicPointer = (weapondata_basic_t*) ary_weaponData[weapon_DynaRef.weaponID];
weapondata_basic_t basicRef = *(basicPointer);
BOOLEAN hasAkimbo = FALSE; //really only used if we bother to do this check.
if(bestSlotYet <= 2){
//check for akimbo...
//hold on, do a few checks before we allow this.
if((basicRef.iAkimboID > 0 && basicRef.iAkimboID < WEAPON_AKIMBO_UPGRADE_ID::LAST_ID ) && (weapon_DynaRef.iBitsUpgrade & BITS_WEAPONOPT_AKIMBO) && (basicRef.iBitsUpgrade & BITS_WEAPONOPT_AKIMBO) ){
//yes.
hasAkimbo = TRUE;
}
}
//We also give a preference to weapons with akimbo.
if(basicRef.iInventorySlot > bestSlotYet || (hasAkimbo) ){
bestSlotYet = basicRef.iInventorySlot;
weaponDynamicID_toEquip = i;
}
}//END OF for loop through all inventory weapons
// it is ok to blank the current weapon too (when _toEquip is -1)
// The "TRUE" is for using akimbo, if the default weapon
// we picked has it. It's fine if not, then this does nothing.
// Also, the shared version does not send a message to clientside to update the viewmodel,
// this causes issues with the grenade removing itself on running out of ammo.
// Throwing knives can use either version here though, but everything works with _shared.
// Plus changes to what's serverside are sent to the client soon enough anyway, doing so here
// seems unnecessary.
//TAGGG - TODO, CRITICAL. Check other places.
// Could anywhere else benefit from TS_playerEquippedWeapon being changed to TS_playerEquippedWeapon_Shared ?
TS_playerEquippedWeapon_Shared(pl, weaponDynamicID_toEquip, TRUE);
}//playerEquipIdeal
// Call to above with a few extra lines included, commonly used so bundled into a method
void playerEquipIdealSafe(player localPlayer){
TS_resetViewModel(localPlayer);
localPlayer.setInventoryEquippedIndex(-1);
playerEquipIdeal(localPlayer);
printfline("playerEquipIdealSafe: stats, activeWeap:%d invEqIndex:%i", localPlayer.activeweapon, localPlayer.inventoryEquippedIndex);
}

19
src/shared/event_enum.h Normal file
View file

@ -0,0 +1,19 @@
enum EVENT_TS{
SPAWN = EV_SEPARATOR,
RESET_VIEW_MODEL,
RESET_PLAYER,
// Is this in nuclide now? I don't think so, verify.
// Same for EXPLOSION / SHAKE below. DROP_WEAPON probably is if we use
// the nuclide way for that.
PLAYER_DEATH,
EFFECT_EXPLOSION,
EFFECT_SHAKE,
DROP_WEAPON,
//TAGGG - NEW. Like EV_IMPACT but to paint a decal only.
EV_PLACEDECAL,
EV_IMPACT_MELEE,
TEST,
};

66
src/shared/flags.h Normal file
View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//TAGGG - NOTE. These are used for the "gflags" variable on all (most?) entities.
// the "flags" variable uses the ones from nuclide defines instead and should not be used with any
// new constants, or ones not already in nuclide's own src/shared/flags.
// So here can start over counting from bit #0 because it uses a new var, gflags instead of flags.
// ALSO... nuclide depends on the GF_SEMI_TOGGLED constant being defined somewhere in the gamemod?
// If not any mentions of it in nuclide's src or base/src files that happen to be included
// (base/src/shared/weapons_common.qc comes to mind) will cause errors.
// Strange as gflags should be handled by the gamemod only.
// There is also a new var called "identity" in CBaseEntity, part of Nuclide now.
// identity can be set to "1", and it looks like it is for everything ever made in Nuclide.
// It signifies that the entity can be respawned if the server is looking for things to call
// ".Respawn" on.
// Making a constant here to make that easier to spot when used here.
#define IDENTITY_CANRESPAWN (1<<0)
// Also, no GF_CANRESPAWN anymore. Was used in the old base, now using the IDENTITY_CANRESPAWN thing above.
/* game flags */
#define GF_SEMI_TOGGLED (1<<0)
// TAGGG - added for secondary to also be able to tell if this
// is the first frame being pressed. Needs to be involved a tiny bit
// manually as this one wasn't part of Nuclide.
#define GF_SEMI_SECONDARY_TOGGLED (1<<1)
#define GF_UNUSED3 (1<<2)
#define GF_UNUSED4 (1<<3)
#define GF_UNUSED5 (1<<4)
#define GF_UNUSED6 (1<<5)
#define GF_UNUSED7 (1<<6)
#define GF_UNUSED8 (1<<7)
#define GF_UNUSED9 (1<<8)
#define GF_UNUSED10 (1<<9)
#define GF_UNUSED11 (1<<10)
#define GF_UNUSED12 (1<<11)
#define GF_UNUSED13 (1<<12)
#define GF_UNUSED14 (1<<14)
#define GF_UNUSED15 (1<<16)
#define GF_UNUSED16 (1<<13)
#define GF_UNUSED17 (1<<17)
#define GF_UNUSED18 (1<<18)
#define GF_UNUSED19 (1<<19)
#define GF_UNUSED20 (1<<20)
#define GF_UNUSED21 (1<<21)
#define GF_UNUSED22 (1<<22)
#define GF_UNUSED23 (1<<23)

110
src/shared/fx_blood.qc Normal file
View file

@ -0,0 +1,110 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifdef CLIENT
var float PARTICLE_BLOOD;
var int DECAL_BLOOD;
void
FX_Blood_Init(void)
{
precache_model("sprites/bloodspray.spr");
precache_model("sprites/blood.spr");
PARTICLE_BLOOD = particleeffectnum("part_blood");
DECAL_BLOOD = particleeffectnum("decal_blood.effect");
}
#endif
void
FX_Blood(vector pos, vector color)
{
#ifdef SERVER
WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET);
WriteByte(MSG_MULTICAST, EV_BLOOD);
WriteCoord(MSG_MULTICAST, pos[0]);
WriteCoord(MSG_MULTICAST, pos[1]);
WriteCoord(MSG_MULTICAST, pos[2]);
WriteByte(MSG_MULTICAST, color[0] * 255);
WriteByte(MSG_MULTICAST, color[1] * 255);
WriteByte(MSG_MULTICAST, color[2] * 255);
msg_entity = self;
multicast(pos, MULTICAST_PVS);
#else
static void Blood_Touch(void)
{
if (serverkeyfloat("*bspversion") == BSPVER_HL)
Decals_Place(self.origin, sprintf("{blood%d", floor(random(1,9))));
else {
decal_pickwall(self, self.origin);
pointparticles(DECAL_BLOOD, g_tracedDecal.endpos, g_tracedDecal.normal, 1);
}
remove(self);
}
if (cvar("violence_hblood") <= 0) {
return;
}
env_sprite eBlood = spawn(env_sprite);
setorigin(eBlood, pos);
setmodel(eBlood, "sprites/bloodspray.spr");
eBlood.drawmask = MASK_ENGINE;
eBlood.maxframe = modelframecount(eBlood.modelindex);
eBlood.loops = 0;
eBlood.scale = 1.0f;
#ifdef GS_RENDERFX
eBlood.m_vecRenderColor = color;
eBlood.m_iRenderMode = RM_COLOR;
eBlood.m_flRenderAmt = 1.0f;
#else
eBlood.colormod = color;
#endif
eBlood.framerate = 20;
eBlood.nextthink = time + 0.05f;
for (int i = 0; i < 3; i++) {
env_sprite ePart = spawn(env_sprite);
setorigin(ePart, pos);
setmodel(ePart, "sprites/blood.spr");
ePart.movetype = MOVETYPE_BOUNCE;
ePart.gravity = 0.5f;
ePart.scale = 0.5f;
ePart.drawmask = MASK_ENGINE;
ePart.maxframe = modelframecount(ePart.modelindex);
ePart.loops = 0;
#ifdef GS_RENDERFX
ePart.m_vecRenderColor = color;
ePart.m_iRenderMode = RM_COLOR;
ePart.m_flRenderAmt = 1.0f;
#else
ePart.colormod = color;
#endif
ePart.framerate = 15;
ePart.nextthink = time + 0.1f;
ePart.velocity = randomvec() * 64;
ePart.touch = Blood_Touch;
ePart.solid = SOLID_BBOX;
/* ignore player physics */
ePart.dimension_solid = 1;
ePart.dimension_hit = 1;
setsize(ePart, [0,0,0], [0,0,0]);
}
#endif
}

164
src/shared/fx_breakmodel.qc Normal file
View file

@ -0,0 +1,164 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifdef CLIENT
void
FX_BreakModel_Init(void)
{
precache_model("models/glassgibs.mdl");
precache_model("models/woodgibs.mdl");
precache_model("models/metalplategibs.mdl");
precache_model("models/fleshgibs.mdl");
precache_model("models/ceilinggibs.mdl");
precache_model("models/computergibs.mdl");
precache_model("models/rockgibs.mdl");
precache_model("models/cindergibs.mdl");
precache_sound("debris/bustglass1.wav");
precache_sound("debris/bustglass2.wav");
precache_sound("debris/bustglass3.wav");
precache_sound("debris/bustcrate1.wav");
precache_sound("debris/bustcrate2.wav");
precache_sound("debris/bustcrate3.wav");
precache_sound("debris/bustmetal1.wav");
precache_sound("debris/bustmetal2.wav");
precache_sound("debris/bustflesh1.wav");
precache_sound("debris/bustflesh2.wav");
precache_sound("debris/bustconcrete1.wav");
precache_sound("debris/bustconcrete2.wav");
precache_sound("debris/bustceiling.wav");
}
#endif
void
FX_BreakModel(int count, vector vMins, vector vMaxs, vector vVel, float fStyle)
{
#ifdef SERVER
WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET);
WriteByte(MSG_MULTICAST, EV_MODELGIB);
WriteCoord(MSG_MULTICAST, vMins[0]);
WriteCoord(MSG_MULTICAST, vMins[1]);
WriteCoord(MSG_MULTICAST, vMins[2]);
WriteCoord(MSG_MULTICAST, vMaxs[0]);
WriteCoord(MSG_MULTICAST, vMaxs[1]);
WriteCoord(MSG_MULTICAST, vMaxs[2]);
WriteByte(MSG_MULTICAST, fStyle);
WriteByte(MSG_MULTICAST, count);
msg_entity = self;
vector vWorldPos;
vWorldPos[0] = vMins[0] + (0.5 * (vMaxs[0] - vMins[0]));
vWorldPos[1] = vMins[1] + (0.5 * (vMaxs[1] - vMins[1]));
vWorldPos[2] = vMins[2] + (0.5 * (vMaxs[2] - vMins[2]));
multicast(vWorldPos, MULTICAST_PVS);
#else
static void FX_BreakModel_Remove(void) { remove(self) ; }
float fModelCount = 0;
vector vecPos;
string sModel = "";
switch (fStyle) {
case GSMATERIAL_GLASS:
case GSMATERIAL_GLASS_UNBREAKABLE:
sModel = "models/glassgibs.mdl";
fModelCount = 8;
break;
case GSMATERIAL_WOOD:
sModel = "models/woodgibs.mdl";
fModelCount = 3;
break;
case GSMATERIAL_METAL:
sModel = "models/metalplategibs.mdl";
fModelCount = 13;
break;
case GSMATERIAL_FLESH:
sModel = "models/fleshgibs.mdl";
fModelCount = 4;
break;
case GSMATERIAL_TILE:
sModel = "models/ceilinggibs.mdl";
fModelCount = 4;
break;
case GSMATERIAL_COMPUTER:
sModel = "models/computergibs.mdl";
fModelCount = 15;
break;
case GSMATERIAL_ROCK:
sModel = "models/rockgibs.mdl";
fModelCount = 3;
break;
default:
case GSMATERIAL_CINDER:
sModel = "models/cindergibs.mdl";
fModelCount = 9;
break;
}
vector vWorldPos;
vWorldPos = vMins + (0.5 * (vMaxs - vMins));
switch (fStyle) {
case GSMATERIAL_GLASS:
pointsound(vWorldPos, sprintf("debris/bustglass%d.wav", random(1, 4)), 1.0f, ATTN_NORM);
break;
case GSMATERIAL_WOOD:
pointsound(vWorldPos, sprintf("debris/bustcrate%d.wav", random(1, 4)), 1.0f, ATTN_NORM);
break;
case GSMATERIAL_METAL:
case GSMATERIAL_COMPUTER:
pointsound(vWorldPos, sprintf("debris/bustmetal%d.wav", random(1, 3)), 1.0f, ATTN_NORM);
break;
case GSMATERIAL_FLESH:
pointsound(vWorldPos, sprintf("debris/bustflesh%d.wav", random(1, 3)), 1.0f, ATTN_NORM);
break;
case GSMATERIAL_CINDER:
case GSMATERIAL_ROCK:
pointsound(vWorldPos, sprintf("debris/bustconcrete%d.wav", random(1, 4)), 1.0f, ATTN_NORM);
break;
case GSMATERIAL_TILE:
pointsound(vWorldPos, "debris/bustceiling.wav", 1.0f, ATTN_NORM);
break;
}
for (int i = 0; i < count; i++) {
entity eGib = spawn();
eGib.classname = "gib";
vecPos[0] = vMins[0] + (random() * (vMaxs[0] - vMins[0]));
vecPos[1] = vMins[1] + (random() * (vMaxs[1] - vMins[1]));
vecPos[2] = vMins[2] + (random() * (vMaxs[2] - vMins[2]));
setorigin(eGib, vecPos);
setmodel(eGib, sModel);
setcustomskin(eGib, "", sprintf("geomset 0 %f\n", random(1, fModelCount + 1)));
eGib.movetype = MOVETYPE_BOUNCE;
eGib.solid = SOLID_NOT;
eGib.avelocity[0] = random()*600;
eGib.avelocity[1] = random()*600;
eGib.avelocity[2] = random()*600;
eGib.think = FX_BreakModel_Remove;
eGib.nextthink = time + 10;
if ((fStyle == GSMATERIAL_GLASS) || (fStyle == GSMATERIAL_GLASS_UNBREAKABLE)) {
eGib.alpha = 0.5f;
}
eGib.drawmask = MASK_ENGINE;
}
#endif
}

Some files were not shown because too many files have changed in this diff Show more