ts/src/client/view.qc

744 lines
27 KiB
Plaintext

/*
* 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.
*/
// 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.
// 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;
if(autocvar_cl_printoutspam == 1){
printfline("STATUS: %i==%d - %i", pSeat->m_iLastWeapon, pl.activeweapon, pl.inventoryEquippedIndex);
}
/* only bother upon change */
if (pSeat->m_iLastWeapon == pl.activeweapon) {
/*
if(pl.activeweapon == 0 && pl.inventoryEquippedIndex != -1){
// what??
pl.setInventoryEquippedIndex(pl.inventoryEquippedIndex);
}else{
TS_View_RoutineCheck();
return;
}
*/
TS_View_RoutineCheck();
return;
}
printfline("View_UpdateWeapon: change detected: %i -> %d", pSeat->m_iLastWeapon, pl.activeweapon);
printfline("and how about the others %d %i", pl.activeweapon, pl.inventoryEquippedIndex);
pSeat->m_iOldWeapon = pSeat->m_iLastWeapon;
pSeat->m_iLastWeapon = pl.activeweapon;
if (!pl.activeweapon /*|| pl.inventoryEquippedIndex < 0*/) {
// can't work with this!
TS_View_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
// ... And yes, it seems this bound check is indeed needed, zany sync issues.
if(pl.inventoryEquippedIndex != -1){
Weapons_Draw();
}
TS_View_ChangeViewModelPost();
}
void
TS_View_SetViewModelFromStats(void)
{
player pl = (player)pSeat->m_ePlayer;
entity vm = pSeat->m_eViewModel;
entity mflash = pSeat->m_eMuzzleflash;
entity mflashAkimbo = pSeatLocal->m_eMuzzleflashAkimbo;
weapondata_basic_t* basicP;
weapondynamic_t dynaRef;
if(pl.inventoryEquippedIndex == -1){
// what.
return;
}
//printfline("TS_View_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.iForceBodygroup1Submodel > 0){
cumulativeCommandString = sprintf("%sgeomset 0 %i\n", cumulativeCommandString, dynaRef.iForceBodygroup1Submodel );
}
// no need to do that geomset with the same value again.
pl.prev_iForceBodygroup1Submodel = dynaRef.iForceBodygroup1Submodel;
setcustomskin(vm, "", cumulativeCommandString);
TS_View_ChangeViewModelPost();
// Don't let View_UpdateWeapon do this all over.
pSeat->m_iLastWeapon = pl.activeweapon;
}//TS_View_SetViewModelFromStats
// On any frame, see if something about the current viewmodel needs to be changed
void
TS_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_iForceBodygroup1Submodel != dynaRef.iForceBodygroup1Submodel){
if(dynaRef.iForceBodygroup1Submodel > 0){
string commandString;
commandString = sprintf("geomset 0 %i\n", dynaRef.iForceBodygroup1Submodel );
setcustomskin(vm, "", commandString);
}
pl.prev_iForceBodygroup1Submodel = dynaRef.iForceBodygroup1Submodel;
}
}//TS_View_RoutineCheck
// Used to be in View_UpdateWeapon, now also called for manual changes clientside too
// (TS_View_SetViewModelFromStats above).
void
TS_View_ChangeViewModelPost(void)
{
player pl = (player)pSeat->m_ePlayer;
entity vm = pSeat->m_eViewModel;
entity mflash = pSeat->m_eMuzzleflash;
entity mflashAkimbo = pSeatLocal->m_eMuzzleflashAkimbo;
/* 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);
SAVE_STATE(pl.iZoomLevel);
//SAVE_STATE(pl.flZoomTarget);
//TAGGG - also new line. It is a good idea to reset the event
// on changing models, right?
pSeat->m_pEventCall = NULL;
skel_delete( mflash.skeletonindex );
mflash.skeletonindex = skel_create( vm.modelindex );
pSeat->m_iVMBones = skel_get_numbones( mflash.skeletonindex ) + 1;
///skel_delete(mflashAkimbo.skeletonindex);
// could we just reuse the mflash.skeletonindex here?
//mflashAkimbo.skeletonindex = skel_create(vm.modelindex);
mflashAkimbo.skeletonindex = mflash.skeletonindex;
// no need to set m_iVMBones again, nor have a separate copy. How could it
// be any different for the akimbo one (clone of mflash)
}//TS_View_ChangeViewModelPost
void
TS_View_ResetViewModel(void)
{
printfline("TS_View_ResetViewModel called.");
if(pSeat->m_ePlayer == NULL){
printfline("TS_View_ResetViewModel: early end #1, client ent NULL");
return;
}
// was m_eViewModel and m_eMuzzleflash
entity vm = pSeat->m_eViewModel;
entity mflash = pSeat->m_eMuzzleflash;
entity mflashAkimbo = pSeatLocal->m_eMuzzleflashAkimbo;
setmodel( vm, "" );
skel_delete( mflash.skeletonindex );
mflash.skeletonindex = 0;
pSeat->m_iVMBones = 0 + 1;
// ! If the skeletonindex becomes shared, skel_delete for each will not be necessary
//skel_delete(mflashAkimbo.skeletonindex);
mflashAkimbo.skeletonindex = 0;
/*
player pl = (player) pSeat->m_ePlayer;
pl.setInventoryEquippedIndex(-1); //do we have to?
*/
}// TS_View_ResetViewModel
// NEW, helper method, not called by Nuclide
// Does the same Nuclide script to draw the normal viewmodel at viewzooms between 0.5
// and 1.0, Nuclide skips drawing it on any zoom below 1.0 unlike original TS.
// Also, handles drawing the akimbo muzzle flash.
// Do not call from View_UpdateWeapon. Calling from LatePreDraw (new TS pseudo-event)
// is best.
// Also this is "Draw" as in render, not equip a weapon
void
TS_View_DrawCustom(player pl){
// Same forbidding conditions from Nuclide's src/client/view.qc
if (pl.health <= 0) {
return;
}
if (cvar("r_drawviewmodel") == 0 || autocvar_cl_thirdperson == TRUE) {
return;
}
// Nuclide does not draw the viewmodel at any zoom other than 1.0 (unmodified).
// So, this draws the viewmodel the othertimes TS did, in lower zoom choices.
// At viewzoom 0.5 and above, the scope graphic is not drawn so it is safe to
// do this.
if(pl.viewzoom >= 0.5){
entity m_eViewModel = pSeat->m_eViewModel;
entity m_eMuzzleflash = pSeat->m_eMuzzleflash;
entity m_eMuzzleflashAkimbo = pSeatLocal->m_eMuzzleflashAkimbo;
// (only re-do the default Nuclide viewmodel + normal muzzleflash script if we have reason
// to believe Nuclide didn't, i.e., a zoom under 1.0)
if(pl.viewzoom < 1.0){
// CLONE OF NUCLIDE SCRIPT
/////////////////////////////////////////////////////////////////////////////
if (m_eMuzzleflash.alpha > 0.0f) {
makevectors(getproperty(VF_ANGLES));
m_eMuzzleflash.origin = gettaginfo(m_eViewModel, m_eMuzzleflash.skin);
m_eMuzzleflash.angles = m_eViewModel.angles;
m_eMuzzleflash.angles[2] += (random() * 10) - 5;
/*dynamiclight_add(pSeat->m_vecPredictedOrigin + (v_forward * 32), 400 * m_eMuzzleflash.alpha, [1,0.45,0]);*/
setorigin(m_eMuzzleflash, m_eMuzzleflash.origin);
addentity(m_eMuzzleflash);
}
setorigin(m_eViewModel, m_eViewModel.origin);
addentity(m_eViewModel);
/////////////////////////////////////////////////////////////////////////////
}
// Regardless of being 1.0 or not (Nuclide certainly isn't doing this),
// handle the other muzzleflash too
if (m_eMuzzleflashAkimbo.alpha > 0.0f) {
makevectors(getproperty(VF_ANGLES));
m_eMuzzleflashAkimbo.origin = gettaginfo(m_eViewModel, m_eMuzzleflashAkimbo.skin);
m_eMuzzleflashAkimbo.angles = m_eViewModel.angles;
m_eMuzzleflashAkimbo.angles[2] += (random() * 10) - 5;
/*dynamiclight_add(pSeat->m_vecPredictedOrigin + (v_forward * 32), 400 * m_eMuzzleflashAkimbo.alpha, [1,0.45,0]);*/
setorigin(m_eMuzzleflashAkimbo, m_eMuzzleflashAkimbo.origin);
addentity(m_eMuzzleflashAkimbo);
}
}
if(pl.bShellEjectScheduled){
pl.bShellEjectScheduled = FALSE;
// TODO - how about a first-person check? Doesn't make sense if in thirdperson.
// Although that might've been needed earlier unless this event-thing works fine for being
// a playermodel too. Players other than the local one being rendered and needing to drop
// shells, that sounds like a whole other story
if(pl.iShellEjectType != SHELLEJECT_ID::NONE){
CTSShellEject::generateForViewModelAkimbo(pl.iShellEjectType, pl.iShellEjectAkimboChoice);
}
}
}
// For drawing the special effects for one of the weapons.
// Not to be confused with TS_)View_DrawSpecialEffects, that is called straight from rendering,
// and calls this once for sigular weapons, twice for akimbo. Modular to avoid duplicate script.
// So yes, a helper method of a helper method.
// ALSO - does not draw the lighting from a flashlight, this only does the glow effect on the
// viewmodel.
void
TS_View_DrawSpecialEffects_Weapon(
player pl, int thirdperson, BOOL canRenderLaserSight, BOOL canRenderFlashlight, vector posView,
vector angView, vector gunpos, vector angGun, vector shortForwardEndRelative,
vector* recentLaserHitPosVar
)
{
const float drawAlpha = 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 shortForwardEnd;
vector flashPos;
makevectors(angGun);
rotatevectorsbyangle( [-0.45, 0.27, 0] );
flashPos = gunpos;
flashPos += v_up * -0.08;
// HACK - for now this will do
if(shortForwardEndRelative.y >= 0){
flashPos += v_right * 0.06;
}else{
flashPos += v_right * -0.06;
}
shortForwardEnd = gunpos;
shortForwardEnd += v_forward * shortForwardEndRelative.x;
shortForwardEnd += v_right * shortForwardEndRelative.y;
shortForwardEnd += v_up * shortForwardEndRelative.z;
traceline(posView, shortForwardEnd, FALSE, pl);
// Is there a clear path from posView to a little away from the gunpos? Required for these
// effects to even be attempted.
// It is then used for trace_endpos
if(trace_fraction >= 1.0){
traceline(shortForwardEnd, shortForwardEnd + v_forward * 1024, MOVE_HITMODEL, pl);
// other places care about this.
if (pl.entnum == player_localentnum && recentLaserHitPosVar != NULL) {
//pl.recentLaserHitPos = trace_endpos;
(*recentLaserHitPosVar) = trace_endpos;
}
if(canRenderLaserSight){
if (!thirdperson) {
// relying on v_ direction globals as set by makevectors(angGun) earlier
R_BeginPolygon("sprites/laserbeam.spr_0.tga", 1, 0);
// Looks like just being offset by -1 in the Y direction for both begin and end vertices is best,
// instead of negatives for the first two and negatives for the last two PolygonVertex calls.
// In that old way, getting close to a wall causes the laser to look like it's going upward.
// Not sure where the idea for that came from to begin with, maybe other places could always add
// in a constant direction and do away with 'fsize'-like things. But low priority for now.
R_PolygonVertex(gunpos + v_right * fsize[0] - v_up * fsize[1], [1,1], lasColor, drawAlpha);
R_PolygonVertex(gunpos - v_right * fsize[0] - v_up * fsize[1], [0,1], lasColor, drawAlpha);
R_PolygonVertex(trace_endpos - v_right * fsize[0] - v_up * fsize[1], [0,0], lasColor, drawAlpha);
R_PolygonVertex(trace_endpos + v_right * fsize[0] - v_up * fsize[1], [1,0], lasColor, drawAlpha);
R_EndPolygon();
}
// The larger laser dot graphic that only players other than the current one see,
// because this still receives calls to draw weapons for other players.
// Not to be confused with the laser dot drawn on the HUD for the local player.
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();
}
}
// Flashlight glow-sprite effect on the viewmodel, does not really involve lighting, that is elsewhere.
// Pretty sure original TS has nothing like this for third person or looking at other players.
if(canRenderFlashlight){
if(!thirdperson){
makevectors(angView);
//trace_endpos += trace_plane_normal * fsizeDot[0]/6;
R_BeginPolygon("sprites/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();
}
}
}// trace pre-check
}// TS_View_DrawSpecialEffects_Weapon
// another helper method
// Draw the lasersight, and flashlight effects
// IDEA: any way to give the effect of RF_DEPTHHACK (always draw on top of the map
// so that flashlight muzzle glow-effects don't clip through the map), or the RF_ADDITIVE
// that muzzle flashes use for being an effect in general? Probably unnecessary anyway.
// Flashlight glow effects do clip through the map in original TS so not a huge priority anyway,
// and showing up in front of the weapon would not be amazing.
// Effects like muzzleflashes don't even do anything special with depth-hackery so, eh.
void
TS_View_DrawExtraEffects(player pl, int thirdperson)
{
if(pl.inventoryEquippedIndex > -1){
weapondynamic_t dynaRef2 = pl.ary_myWeapons[pl.inventoryEquippedIndex];
//printfline("---TS_View_DrawSpecialEffects_Weapon--- %d", (float)((dynaRef2.iBitsUpgrade_on & BITS_WEAPONOPT_FLASHLIGHT) != 0));
//dynaRef2.iBitsUpgrade_on = 0;
}
//int thirdperson = (autocvar_cl_thirdperson == TRUE || this.entnum != player_localentnum);
//base_player pp = (base_player)this;
BOOL canRenderFlashlight = FALSE;
BOOL canRenderLaserSight = FALSE;
vector posView;
vector angView;
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];
BOOL canDrawAkimboEffects = FALSE;
pl.recentLaserHitPosSet = TRUE;
// 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?
//we're going to use the buyopts of our current weapon + the one actually turned on, yah?
/*
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* baseP = pl.getEquippedWeaponData();
// 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 & baseP->iBitsUpgrade));
if(legalBuyOpts_on & BITS_WEAPONOPT_FLASHLIGHT){
canRenderFlashlight = TRUE;
}
if(legalBuyOpts_on & BITS_WEAPONOPT_LASERSIGHT){
canRenderLaserSight = TRUE;
}
}// _GUN or _IRONSIGHT type checks
}// weaponEquippedID check
// DEBUG
//canRenderLaserSight = TRUE;
//canRenderFlashlight = 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];
// Why not just that then, why the minus 8? We want positions exactly to start where
// the viewOFS is.
posView = pSeat->m_vecPredictedOrigin + pl.view_ofs;
angView = view_angles;
// CHECK: is "getproperty(VF_CL_VIEWANGLES)" always the same as "view_angles"?
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{
canDrawAkimboEffects = 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 );
}
// Draw the flashlight lighting here, it is only done once regardless of akimbo-ness after all.
if(canRenderFlashlight){
// 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){
// too far, no light at all
flashlightBrightnessFactor = 0;
}else 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);
}
}// brightness check
}// flashlight-on check
if(canRenderLaserSight || canRenderFlashlight){
// TRY IT SOMEHOW? RF_DEPTHHACK
//pSeat->m_eViewModel.renderflags = RF_DEPTHHACK;
//if (alpha <= 0.0f) {
// return;
//}
// Singular form.
TS_View_DrawSpecialEffects_Weapon(pl, thirdperson, canRenderLaserSight, canRenderFlashlight, posView, angView, gunpos, angGun, [-1, 0.35, 0.22], &pl.recentLaserHitPos);
// This requires the current weapon to be akimbo, player is in first person,
// and the local player is being rendered.
// How about akimbo too if wanted, first-person only. 3rd person akimbo is not portrayed.
// also, this condition is the same as, if(!thirdperson && pl.weaponEquippedAkimbo)
if(canDrawAkimboEffects){
// Do the 2nd weapon's effects too
TS_View_DrawSpecialEffects_Weapon(pl, thirdperson, canRenderLaserSight, canRenderFlashlight, posView, angView, gunpos2, angGun2, [-1, -0.35, 0.22], &pl.recentLaserHitPos2);
}//END OF akimbo check
/*
if (m_iBeams == 0) {
alpha -= clframetime * 3;
return;
}
*/
}else{
pl.recentLaserHitPosSet = FALSE;
}// canRenderLaserSight || canRenderFlashlight
}
// 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
TS_View_ShowMuzzleflash(int index, int akimboChoice)
{
// use the akimbo choice to tell which one to show, or both
// (NONE means this is a singular weapon)
if(akimboChoice == BITS_AKIMBOCHOICE_NONE || (akimboChoice & BITS_AKIMBOCHOICE_RIGHT)){
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;
}
if(akimboChoice & BITS_AKIMBOCHOICE_LEFT){
pSeatLocal->m_eMuzzleflashAkimbo.modelindex = (float)index;
// Event_ProcessModel: force it.
pSeatLocal->m_eMuzzleflashAkimbo.alpha = 1.0f;
pSeatLocal->m_eMuzzleflashAkimbo.scale = 0.25;
// akimbo attachments are in pairs:
// 0 on one weapon, 1 further out along that weapon's direction,
// 2 on the other weapon, 3 further along the other weapon's direction.
// ALSO: on akimbo weapons, attachments 0 and 1 are for the right weapon,
// 1 and 2 for the left.
pSeatLocal->m_eMuzzleflashAkimbo.skin = pSeat->m_iVMBones + 2;
}
}