Skip to content

Commit

Permalink
fix refactor: game beam handling.
Browse files Browse the repository at this point in the history
+ turns out CSQC is kinda flaky on client-side hit detection, possibly through the dozens of addEntity() the game requires, which I think is causing client-side hit-boxes to change-size in almost every frame.

+ the fix is to rewire beam weapons back to something  more like classic quake. Let the server dictate start/end, and target entity.
  + the only other option would have been dumping start/end for beams into the client's SendEnt func, clunky and spammy.

  + beam graphics need to generally synchronize with unit attacks, so using Item.SendEnt is kinda out too.

+ not super happy about this solution.
  • Loading branch information
Subject9x committed Oct 30, 2024
1 parent b87f7d0 commit b456cae
Show file tree
Hide file tree
Showing 15 changed files with 236 additions and 154 deletions.
2 changes: 1 addition & 1 deletion client/main/client_parse_funcs.qc
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ float() CSQC_Parse_TempEntity =
return TRUE;

case TE_VFX_RAIL:
particle_spawn_beam(ReadByte(), te_read_vector(), te_read_vector(), ReadFloat());
particle_spawn_beam(ReadByte(), ReadLong(), te_read_vector(), te_read_vector(), ReadFloat(), ReadByte());
return TRUE;

case TE_PARTICLE_CUBE:
Expand Down
14 changes: 11 additions & 3 deletions client/network/net_general.qc
Original file line number Diff line number Diff line change
Expand Up @@ -431,8 +431,11 @@ void( float prevShield ) sendevent_shield_explode={
*/
void() sendevent_handle_weaponfire={
local entity equip;
local entity unit;
local float wepId;


unit = self;

particleDraw = vlen(CLIENT_vis_org - self.origin);

wepId = 0;
Expand All @@ -441,8 +444,13 @@ void() sendevent_handle_weaponfire={
equip = sendent_get_weapon( bitshift(1, wepId) );

if( equip ){
if( (self.attackFlag & equip.w_group) && equip.itemMuzzleFlash ){
te_weapon_fire_handler(equip);
if( (self.attackFlag & equip.w_group) && equip.beamFlag == FALSE ){
te_weapon_fire_handler(equip, self);
self = equip;
if(self.itemMuzzleFlash){
self.itemMuzzleFlash();
}
self = unit;
}
}

Expand Down
4 changes: 2 additions & 2 deletions client/util/headers/particle_sys.qh
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,9 @@ void (vector from, vector to, float thickness, string texture, float aspect, flo
//ported from effectinfo_api
void( vector org, vector windVel ) te_mech_crit;

void( entity item )te_weapon_fire_handler;
void( entity item, entity parent )te_weapon_fire_handler;

void(float beamType, vector particleOrg, vector particleEnd, float width) particle_spawn_beam;
void(float beamType, float entId, vector particleOrg, vector particleEnd, float width, float impactType) particle_spawn_beam;

//void() te_explode_mech_piece_th;
//void(entity piece) te_explode_mech_piece;
Expand Down
108 changes: 75 additions & 33 deletions client/util/particle_sys.qc
Original file line number Diff line number Diff line change
Expand Up @@ -242,45 +242,43 @@ void() cl_particles_fogFrame={
localcmd(newFog);*/
};

void(entity item) te_weapon_fire_handler={
void(entity item, entity parent) te_weapon_fire_handler={
local entity this;
local entity parent;
local vector unitOrg;
local vector view;
local vector adjPartOffset;
local vector offset;

view = self.rootAngle;

view = parent.rootAngle;
view_x = 0;
makevectors(view);

this = self;
self = item;
util_getPartFromName( self.partParentId, this);
self = this;

if(this.data_type == DATA_VEHC){
unitOrg = this.cacheGroundPos;
if(parent.data_type == DATA_VEHC){
unitOrg = parent.cacheGroundPos;
}
else{
unitOrg = this.rootOrigin;
unitOrg = parent.rootOrigin;
}
offset = unitOrg + (v_right * T_PART_PARENT_OFFSET_x) + (v_up * T_PART_PARENT_OFFSET_y) + (v_forward * T_PART_PARENT_OFFSET_z);

//FPV z-offset is corrected at the parent-part origin step!
if(this.clientLocalNum == player_localentnum && CLIENT_server_ent.chaseActive == FALSE){
offset = offset + (v_forward * this.cameraOffset_z);
if(parent.clientLocalNum == player_localentnum && CLIENT_server_ent.chaseActive == FALSE){
offset = offset + (v_forward * parent.cameraOffset_z);
}
adjPartOffset = self.compOffset - T_PART_PARENT_OFFSET;
adjPartOffset = item.compOffset - T_PART_PARENT_OFFSET;

view = T_PART_PARENT_ANGL;
view_x = view_x *-1;
makevectors( view);
offset = offset + (v_up * adjPartOffset_y) + (v_right * adjPartOffset_x) + (v_forward * adjPartOffset_z);

self.wepFireOffset = offset + (v_forward * self.wepFireOffsetCache_z) + (v_right * self.wepFireOffsetCache_x) + (v_up * self.wepFireOffsetCache_y);
self.itemMuzzleFlash();
self = this;
item.wepFireOffset = offset + (v_forward * item.wepFireOffsetCache_z) + (v_right * item.wepFireOffsetCache_x) + (v_up * item.wepFireOffsetCache_y);
};

/*
Expand Down Expand Up @@ -1103,6 +1101,16 @@ void() particle_beam_las_draw={
Draw_CylindricLine(self.beamOrg, self.beamEnd, self.beamWidth, "particles/laserbeam", 0.5, time * (random() * -6), self.drawcolor1, 0.75, DRAWFLAG_NORMAL | DRAWFLAG_MIPMAP, CLIENT_vis_org);
};

void() particle_beam_pac_draw={
if(vlen(self.beamOrg - CLIENT_vis_org) > PARTICLE_HAZ_DISTANCE){
return;
}
if(self.beamActive == FALSE){
return;
}
Draw_CylindricLine(self.beamOrg, self.beamEnd, self.beamWidth, "particles/laserbeam", 0.5, time * (random() * -6), self.drawcolor1, 0.75, DRAWFLAG_NORMAL | DRAWFLAG_MIPMAP, CLIENT_vis_org);
};

void() particle_beam_lightning_draw={
local float segCount;
local vector bAngl;
Expand Down Expand Up @@ -1146,36 +1154,70 @@ void() particle_beam_lightning_draw={

};



//float beamType, vector particleOrg, vector particleEnd, float width, float aspect, float shift, vector colr, float alph
void(float beamType, vector particleOrg, vector particleEnd, float width) particle_spawn_beam={
void(float beamType, float entId, vector particleOrg, vector particleEnd, float width, float impactType) particle_spawn_beam={

local entity ent;
local entity this;

ent = spawn();
//'WORLD' beam effects, not bound to weapons
if(entId == 0){
ent = spawn();
if(!ent){
return;
}
if(beamType == BEAM_LAS){
ent.predraw = particle_beam_las_draw;
ent.nextthink = time + 0.2;
}
else if(beamType == BEAM_ESR){
ent.predraw = particle_beam_lightning_draw;
ent.nextthink = time + 0.075;
}
else if( beamType == BEAM_PAC){
ent.predraw = particle_beam_pac_draw;
ent.nextthink = 0.5;
}
else{
//no-op, bad beam type
return;
}
ent.drawmask = MASK_NORMAL;
ent.origin = particleOrg;
ent.solid = SOLID_NOT;
ent.movetype = MOVETYPE_NONE;
setsize(ent, '-1 -1 -1', '1 1 1');
setorigin(ent, ent.origin);
ent.beamOrg = particleOrg;
ent.beamEnd = particleEnd;
ent.beamWidth = width;
ent.beamActive = TRUE;
ent.think = particle_beam_th;
return;
}

//WEAPON beam effects
ent = findfloat(world, entnum, fabs(rint(entId)));
if(!ent){
return;
}
if(beamType == 0){
ent.predraw = particle_beam_las_draw;
ent.nextthink = time + 0.2;
if(!ent.owner){
return;
}
else{
ent.predraw = particle_beam_lightning_draw;
ent.nextthink = time + 0.075;
}
ent.drawmask = MASK_NORMAL;
ent.origin = particleOrg;
ent.solid = SOLID_NOT;
ent.movetype = MOVETYPE_NONE;
setsize(ent, '-1 -1 -1', '1 1 1');
setorigin(ent, ent.origin);
ent.beamOrg = particleOrg;
if(ent.beamActive){
return;
}

ent.beamEnd = particleEnd;
ent.beamWidth = width;
ent.beamActive = TRUE;
ent.think = particle_beam_th;
ent.pcl_count = impactType;

te_weapon_fire_handler(ent, ent.owner);
this = self;
self = ent;
if(self.itemMuzzleFlash){
self.itemMuzzleFlash();
}
self = this;

};

Expand Down
5 changes: 5 additions & 0 deletions common/data/data_values_particles.qc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ Overview:
*/

//BEAMS
#define BEAM_LAS 0
#define BEAM_ESR 1
#define BEAM_PAC 2

//Projectile Impact Events
#define IMPACT_SKY 0
#define IMPACT_DIRT 1
Expand Down
1 change: 1 addition & 0 deletions common/data/defs/defs_item.qc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Overview
.void() itemMuzzleFlash;
#endif

.float beamFlag; //<sigh>
.float w_isburst; //ugh
.float w_currentammo;
.float burstRate; //percentage of reloadRate to use for burst-fire intervals.
Expand Down
77 changes: 43 additions & 34 deletions common/data/item/weapon/data_electro_repeater.qc
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ void() data_ini_attack_electro_repeater_;

#ifdef CSQC
void() te_beam_esr={
local float len;
local float segCount;
local vector bAngl;
local float segment;
Expand All @@ -118,29 +119,36 @@ void() te_beam_esr={

segCount = 6;
width = self.beamWidth;
segment = rint(vlen(self.beamEnd - self.wepFireOffset) / segCount);
len = vlen(self.beamEnd - self.wepFireOffset);
segment = rint(len / segCount);
segOrg = self.wepFireOffset;

bAngl = vectoangles(self.beamEnd - self.wepFireOffset);
bAngl_x = bAngl_x * -1;

makevectors(bAngl);
nexOrg = self.wepFireOffset + (v_forward * segment) + (v_right * crandom() * 4) + (v_up * crandom() * 6);
Draw_CylindricLine(self.wepFireOffset, nexOrg, width, "particles/electro_beam", 0.1, time * 0.1, '0.17 0.33 1.0', 1.0, DRAWFLAG_NORMAL | DRAWFLAG_MIPMAP, CLIENT_vis_org);
segOrg = nexOrg;
segCount = segCount - 1;
if(len > 100){
nexOrg = self.wepFireOffset + (v_forward * segment) + (v_right * crandom() * 4) + (v_up * crandom() * 6);
Draw_CylindricLine(self.wepFireOffset, nexOrg, width, "particles/electro_beam", 0.1, time * 0.1, '0.17 0.33 1.0', 1.0, DRAWFLAG_NORMAL | DRAWFLAG_MIPMAP, CLIENT_vis_org);


segOrg = nexOrg;
segCount = segCount - 1;

for( itr = segCount; itr < (segment-2); itr = itr + 1){
nexOrg = self.wepFireOffset + (v_forward * segment * itr) + (v_right * crandom() * 2) + (v_up * crandom() * 4);
for( itr = segCount; itr < (segment-2); itr = itr + 1){
nexOrg = self.wepFireOffset + (v_forward * segment * itr) + (v_right * crandom() * 2) + (v_up * crandom() * 4);

Draw_CylindricLine(segOrg, nexOrg, width, "particles/electro_beam", 0.1, time * 0.1, '0.17 0.33 1.0', 1.0, DRAWFLAG_NORMAL | DRAWFLAG_MIPMAP, CLIENT_vis_org);

width = max(1, width - 1);
Draw_CylindricLine(segOrg, nexOrg, width, "particles/electro_beam", 0.1, time * 0.1, '0.17 0.33 1.0', 1.0, DRAWFLAG_NORMAL | DRAWFLAG_MIPMAP, CLIENT_vis_org);
width = max(1, width - 1);

segOrg = nexOrg;
segOrg = nexOrg;
}
}
else{
segOrg = self.wepFireOffset;
}
Draw_CylindricLine(segOrg, self.beamEnd, width, "particles/electro_beam", 0.1, time * 0.1, '0.17 0.33 1.0', 1.0, DRAWFLAG_NORMAL | DRAWFLAG_MIPMAP, CLIENT_vis_org);

};
void() te_muzzle_esr={
local vector startOrg;
Expand All @@ -149,33 +157,27 @@ void() te_muzzle_esr={
local float impactStyle;

startOrg = self.wepFireOffset;

traceline(startOrg, self.owner.laser_sight_org, FALSE, self.owner);
endOrg = trace_endpos;
endOrg = self.beamEnd;

norm = normalize(endOrg - startOrg);

sound7(self, CHAN_AUTO, self.fire_sound, 1, ATTN_NORM, (0.5 + (random() * 0.5)) * 175, 0 );

//trailparticles( world, particleeffectnum("TE_ESR_RAIL"), startOrg, endOrg );

if( particleDraw < PARTICLE_HAZ_DISTANCE / 2){
pointparticles( particleeffectnum("TE_ESR_MUZZLE"), startOrg, norm* 1, 1);

impactStyle = te_impact_type(endOrg, trace_ent, trace_dphitcontents);

if(impactStyle == IMPACT_ARMOR){
if(self.pcl_count == IMPACT_ARMOR){
pointparticles( particleeffectnum("TE_ESR_IMPACT_ARMOR"), endOrg, norm * -6, 1);
}
if(impactStyle == IMPACT_DIRT){
if(self.pcl_count == IMPACT_DIRT){
pointparticles( particleeffectnum("TE_ESR_IMPACT_DIRT"), endOrg, norm * -4, 1);
pointparticles(PTC_HIT_DIRT_ENE_SML, endOrg, trace_plane_normal * -1, 1);
}
}

self.beamTime = time + 0.05;
self.beamTime = time + 0.02;
self.beamOrg = startOrg;
self.beamEnd = endOrg;
self.beamWidth = 4.0;
self.beamActive = TRUE;
};
Expand All @@ -199,6 +201,7 @@ void() data_ini_electro_repeater_={
self.spreadDefault = '0.1 0.1 0';
self.wepFireOffset = '0 0 8';
self.burstRate = 0;
self.beamFlag = TRUE;

precache_sound("sound/weapons/esr_ovrht.ogg");
#ifdef CSQC
Expand Down Expand Up @@ -237,7 +240,7 @@ void() data_ini_electro_repeater_={

#ifdef SSQC
void() data_ini_attack_electro_repeater_={

local float impactStyle;
if( self.owner.energy <= self.energyRate ){
self.wepReloadState = RELOADING;
self.w_currentammo = self.reloadMax / 1.125;
Expand Down Expand Up @@ -271,20 +274,26 @@ void() data_ini_attack_electro_repeater_={
}
ctrl_weapon_fireOffset();

impactStyle = IMPACT_SKY;
self.owner.attackFlag = self.owner.attackFlag | self.w_group;
if( (trace_dphitcontents & DPCONTENTS_SKY) || (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY) ){
return;
}
if( trace_ent.takedamage ){
if( trace_ent.shield > 0 ){
t_damage_shield(trace_ent, self, self.owner, self.damageValue * 0.5, FIRE_ENDPOINT, FALSE);
}
else{
HIT_LOCATION = t_damage(trace_ent, self, self.owner, self.damageValue, FIRE_ENDPOINT, '0 0 0');
if( HIT_LOCATION && !(self.owner.statTargetHitParts & HIT_LOCATION) ){
self.owner.statTargetHitParts = self.owner.statTargetHitParts | HIT_LOCATION;
if( !(trace_dphitcontents & DPCONTENTS_SKY) && !(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY) ){
impactStyle = IMPACT_DIRT;

if( trace_ent.takedamage ){
impactStyle = IMPACT_ARMOR;

if( trace_ent.shield > 0 ){
t_damage_shield(trace_ent, self, self.owner, self.damageValue * 0.5, FIRE_ENDPOINT, FALSE);
impactStyle = IMPACT_SHIELD;
}
else{
HIT_LOCATION = t_damage(trace_ent, self, self.owner, self.damageValue, FIRE_ENDPOINT, '0 0 0');
if( HIT_LOCATION && !(self.owner.statTargetHitParts & HIT_LOCATION) ){
self.owner.statTargetHitParts = self.owner.statTargetHitParts | HIT_LOCATION;
}
}
}
}
client_send_particle_rail( BEAM_ESR, num_for_edict(self), FIRE_ORIGIN, FIRE_ENDPOINT, 4, impactStyle);
};
#endif
Loading

0 comments on commit b456cae

Please sign in to comment.