]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
mostly working code to use csqc entities for hook, electro, gauntlet (currently doesn...
authorRudolf Polzer <divverent@alientrap.org>
Wed, 21 Jul 2010 19:24:18 +0000 (21:24 +0200)
committerRudolf Polzer <divverent@alientrap.org>
Wed, 21 Jul 2010 19:24:18 +0000 (21:24 +0200)
qcsrc/client/Main.qc
qcsrc/client/hook.qc
qcsrc/common/constants.qh
qcsrc/server/g_hook.qc
qcsrc/server/w_electro.qc
qcsrc/server/w_laser.qc

index ed851b9c1ff3a08ed5662005a40c7f543375b5c2..dc1f5bb518d757046dbc355d00182c290f0766fa 100644 (file)
@@ -924,6 +924,9 @@ void(float bIsNewEntity) CSQC_Ent_Update =
                case ENT_CLIENT_WARPZONE: WarpZone_Read(bIsNewEntity); break;
                case ENT_CLIENT_WARPZONE_CAMERA: WarpZone_Camera_Read(bIsNewEntity); break;
                case ENT_CLIENT_TRIGGER_MUSIC: Ent_ReadTriggerMusic(); break;
+               case ENT_CLIENT_HOOK: Ent_ReadHook(bIsNewEntity, ENT_CLIENT_HOOK); break;
+               case ENT_CLIENT_LGBEAM: Ent_ReadHook(bIsNewEntity, ENT_CLIENT_LGBEAM); break;
+               case ENT_CLIENT_GAUNTLET: Ent_ReadHook(bIsNewEntity, ENT_CLIENT_GAUNTLET); break;
                default:
                        error(strcat("unknown entity type in CSQC_Ent_Update: ", ftos(self.enttype), "\n"));
                        break;
@@ -1304,10 +1307,6 @@ float CSQC_Parse_TempEntity()
                        Net_ReadRace();
                        bHandled = true;
                        break;
-               case TE_CSQC_BEAM:
-                       Net_GrapplingHook();
-                       bHandled = true;
-                       break;
                case TE_CSQC_SPAWN:
                        Net_ReadSpawn();
                        bHandled = true;
index 9f690d5bc884dc3692b1ebb172cdd6089e74775f..8aad6137c5951ccf5b7c25be2c042dd213f6b360 100644 (file)
@@ -1,17 +1,8 @@
-.vector HookStart;
-.vector HookEnd;
-.float HookKillTime;
-.vector LGBeamStart;
-.vector LGBeamEnd;
-.float LGBeamKillTime;
-.float LGBeamSound;
-.float LGBeamSilent;
-.vector GauntletBeamStart;
-.vector GauntletBeamEnd;
-.float GauntletBeamKillTime;
-.float GauntletBeamSound;
-.float GauntletBeamSilent;
-
+.float HookType; // ENT_CLIENT_*
+.vector origin;
+.vector velocity;
+.float HookSound;
+.float HookSilent;
 
 void Draw_CylindricLine(vector from, vector to, float thickness, string texture, float aspect, float shift, vector rgb, float alpha, float drawflag)
 {
@@ -58,211 +49,183 @@ void Draw_GrapplingHook_trace_callback(vector start, vector hit, vector end)
 void Draw_GrapplingHook()
 {
        vector a, b;
-       string tex;
+       string tex, snd;
        vector rgb;
        float t;
        float s;
        vector vs;
 
-       if(time < self.HookKillTime)
-       {
-               s = cvar("cl_gunalign");
-               if(s != 1 && s != 2 && s != 4)
-                       s = 3; // default value
-               --s;
-               vs = hook_shotorigin[s];
-
-               if(self.sv_entnum == player_localentnum - 1)
-                       a = view_origin + view_forward * vs_x + view_right * -vs_y + view_up * vs_z;
-               else
-                       a = self.HookStart;
-               b = self.HookEnd;
-
-               t = GetPlayerColorForce(self.sv_entnum);
-
-               if(t == COLOR_TEAM1)
-               {
-                       tex = "particles/hook_red";
-                       rgb = '1 .3 .3';
-               }
-               else if(t == COLOR_TEAM2)
-               {
-                       tex = "particles/hook_blue";
-                       rgb = '.3 .3 1';
-               }
-               else if(t == COLOR_TEAM3)
-               {
-                       tex = "particles/hook_yellow";
-                       rgb = '1 1 .3';
-               }
-               else if(t == COLOR_TEAM4)
-               {
-                       tex = "particles/hook_pink";
-                       rgb = '1 .3 1';
-               }
-               else
-               {
-                       tex = "particles/hook_green";
-                       rgb = '.3 1 .3';
-               }
+       InterpolateOrigin_Do();
 
-               Draw_GrapplingHook_trace_callback_tex = tex;
-               Draw_GrapplingHook_trace_callback_rnd = random();
-               WarpZone_TraceBox_ThroughZone(a, '0 0 0', '0 0 0', b, MOVE_NOMONSTERS, world, world, Draw_GrapplingHook_trace_callback);
-               Draw_GrapplingHook_trace_callback_tex = string_null;
+       s = cvar("cl_gunalign");
+       if(s != 1 && s != 2 && s != 4)
+               s = 3; // default value
+       --s;
+       switch(self.HookType)
+       {
+               default:
+               case ENT_CLIENT_HOOK:
+                       vs = hook_shotorigin[s];
+                       break;
+               case ENT_CLIENT_LGBEAM:
+                       vs = electro_shotorigin[s];
+                       break;
+               case ENT_CLIENT_GAUNTLET:
+                       vs = gauntlet_shotorigin[s];
+                       break;
        }
 
-       if(time < self.LGBeamKillTime)
+       if((self.owner.sv_entnum == player_localentnum - 1))
        {
-               s = cvar("cl_gunalign");
-               if(s != 1 && s != 2 && s != 4)
-                       s = 3; // default value
-               --s;
-               vs = electro_shotorigin[s];
-
-               if(self.sv_entnum == player_localentnum - 1)
+               switch(self.HookType)
                {
-                       b = view_origin + view_forward * MAX_SHOT_DISTANCE;
-                       WarpZone_TraceLine(view_origin, b, MOVE_NORMAL, world);
-                       a = view_origin + view_forward * vs_x + view_right * -vs_y + view_up * vs_z;
+                       default:
+                       case ENT_CLIENT_HOOK:
+                               a = view_origin + view_forward * vs_x + view_right * -vs_y + view_up * vs_z;
+                               b = self.origin;
+                               break;
+                       case ENT_CLIENT_LGBEAM:
+                       case ENT_CLIENT_GAUNTLET:
+                               b = view_origin + view_forward * MAX_SHOT_DISTANCE;
+                               WarpZone_TraceLine(view_origin, b, MOVE_NORMAL, world);
+                               b = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos);
+                               a = view_origin + view_forward * vs_x + view_right * -vs_y + view_up * vs_z;
+                               break;
                }
-               else
+       }
+       else
+       {
+               switch(self.HookType)
                {
-                       a = self.LGBeamStart;
-                       b = self.LGBeamEnd;
+                       default:
+                       case ENT_CLIENT_HOOK:
+                               a = self.velocity;
+                               b = self.origin;
+                               break;
+                       case ENT_CLIENT_LGBEAM:
+                       case ENT_CLIENT_GAUNTLET:
+                               a = self.origin;
+                               b = self.velocity;
+                               break;
                }
+       }
 
-               tex = "particles/lgbeam";
-               rgb = '1 1 1';
-
-               Draw_GrapplingHook_trace_callback_tex = tex;
-               Draw_GrapplingHook_trace_callback_rnd = random();
-               WarpZone_TraceBox_ThroughZone(a, '0 0 0', '0 0 0', b, MOVE_NORMAL, world, world, Draw_GrapplingHook_trace_callback);
-               Draw_GrapplingHook_trace_callback_tex = string_null;
+       t = GetPlayerColorForce(self.owner.sv_entnum);
 
-               // helps the sound
-               setorigin(self, a);
+       switch(self.HookType)
+       {
+               default:
+               case ENT_CLIENT_HOOK:
+                       if(t == COLOR_TEAM1)
+                       {
+                               tex = "particles/hook_red";
+                               rgb = '1 .3 .3';
+                       }
+                       else if(t == COLOR_TEAM2)
+                       {
+                               tex = "particles/hook_blue";
+                               rgb = '.3 .3 1';
+                       }
+                       else if(t == COLOR_TEAM3)
+                       {
+                               tex = "particles/hook_yellow";
+                               rgb = '1 1 .3';
+                       }
+                       else if(t == COLOR_TEAM4)
+                       {
+                               tex = "particles/hook_pink";
+                               rgb = '1 .3 1';
+                       }
+                       else
+                       {
+                               tex = "particles/hook_green";
+                               rgb = '.3 1 .3';
+                       }
+                       break;
+               case ENT_CLIENT_LGBEAM:
+                       tex = "particles/lgbeam";
+                       rgb = '1 1 1';
+                       break;
+               case ENT_CLIENT_GAUNTLET:
+                       tex = "particles/gauntletbeam";
+                       rgb = '1 1 1';
+                       break;
        }
 
-       if(time < self.GauntletBeamKillTime)
+       Draw_GrapplingHook_trace_callback_tex = tex;
+       Draw_GrapplingHook_trace_callback_rnd = random();
+       WarpZone_TraceBox_ThroughZone(a, '0 0 0', '0 0 0', b, MOVE_NOMONSTERS, world, world, Draw_GrapplingHook_trace_callback);
+       Draw_GrapplingHook_trace_callback_tex = string_null;
+
+       switch(self.HookType)
        {
-               s = cvar("cl_gunalign");
-               if(s != 1 && s != 2 && s != 4)
-                       s = 3; // default value
-               --s;
-               vs = gauntlet_shotorigin[s];
+               default:
+               case ENT_CLIENT_HOOK:
+                       self.angles = vectoangles(b - a);
+                       break;
+               case ENT_CLIENT_LGBEAM:
+               case ENT_CLIENT_GAUNTLET:
+                       break;
+       }
+}
 
-               if(self.sv_entnum == player_localentnum - 1)
-               {
-                       b = view_origin + view_forward * MAX_SHOT_DISTANCE;
-                       WarpZone_TraceLine(view_origin, b, MOVE_NORMAL, world);
-                       a = view_origin + view_forward * vs_x + view_right * -vs_y + view_up * vs_z;
-               }
-               else
-               {
-                       a = self.GauntletBeamStart;
-                       b = self.GauntletBeamEnd;
-               }
+void Remove_GrapplingHook()
+{
+       sound (self, CHAN_PROJECTILE, "misc/null.wav", VOL_BASE, ATTN_NORM);
+}
 
-               tex = "particles/gauntletbeam";
-               rgb = '1 1 1';
+void Ent_ReadHook(float bIsNew, float type)
+{
+       self.HookType = type;
 
-               Draw_GrapplingHook_trace_callback_tex = tex;
-               Draw_GrapplingHook_trace_callback_rnd = random();
-               WarpZone_TraceBox_ThroughZone(a, '0 0 0', '0 0 0', b, MOVE_NORMAL, world, world, Draw_GrapplingHook_trace_callback);
-               Draw_GrapplingHook_trace_callback_tex = string_null;
+       float sf;
+       sf = ReadByte();
 
-               // helps the sound
-               setorigin(self, a);
-       }
+       self.HookSilent = (sf & 0x80);
+       self.iflags = IFLAG_VELOCITY;
 
-       if(time < self.LGBeamKillTime && !self.LGBeamSilent)
-       {
-               if(!self.LGBeamSound)
-               {
-                       sound (self, CHAN_PROJECTILE, "weapons/lgbeam_fly.wav", VOL_BASE, ATTN_NORM);
-                       self.LGBeamSound = 1;
-               }
-       }
-       else
+       InterpolateOrigin_Undo();
+
+       if(sf & 1)
        {
-               if(self.LGBeamSound)
-               {
-                       sound (self, CHAN_PROJECTILE, "misc/null.wav", VOL_BASE, ATTN_NORM);
-                       self.LGBeamSound = 0;
-               }
+               self.owner = playerslots[ReadByte() - 1];
        }
-
-       if(time < self.GauntletBeamKillTime && !self.GauntletBeamSilent)
+       if(sf & 2)
        {
-               if(!self.GauntletBeamSound)
-               {
-                       sound (self, CHAN_PROJECTILE, "weapons/gauntletbeam_fly.wav", VOL_BASE, ATTN_NORM);
-                       self.GauntletBeamSound = 1;
-               }
+               self.origin_x = ReadCoord();
+               self.origin_y = ReadCoord();
+               self.origin_z = ReadCoord();
+               setorigin(self, self.origin);
        }
-       else
+       if(sf & 4)
        {
-               if(self.GauntletBeamSound)
-               {
-                       sound (self, CHAN_PROJECTILE, "misc/null.wav", VOL_BASE, ATTN_NORM);
-                       self.GauntletBeamSound = 0;
-               }
+               self.velocity_x = ReadCoord();
+               self.velocity_y = ReadCoord();
+               self.velocity_z = ReadCoord();
        }
-}
-
-void Net_GrapplingHook()
-{
-       float i, t;
-       vector start, end;
-       entity p;
-
-       i = ReadByte();
-       t = ReadByte();
-       end_x = ReadCoord();
-       end_y = ReadCoord();
-       end_z = ReadCoord();
-       start_x = ReadCoord();
-       start_y = ReadCoord();
-       start_z = ReadCoord();
-
-       if(i <= 0 || i >= 256) // not owned by a client
-               return;
-       --i;
 
-       p = playerslots[i];
-       if(!p)
-               return;
+       InterpolateOrigin_Note();
 
-       switch(t)
+       if(bIsNew)
        {
-               case 0: // hook beam
-                       p.HookKillTime = time + 0.1;
-                       p.HookStart = start;
-                       p.HookEnd = end;
-                       p.draw = Draw_GrapplingHook;
-                       break;
-               case 1: // electro lgbeam
-                       p.LGBeamKillTime = time + 0.1;
-                       p.LGBeamStart = start;
-                       p.LGBeamEnd = end;
-                       p.LGBeamSilent = 0;
-                       p.draw = Draw_GrapplingHook;
-                       break;
-               case 2: // silent electro lgbeam
-                       p.LGBeamKillTime = time + 0.1;
-                       p.LGBeamStart = start;
-                       p.LGBeamEnd = end;
-                       p.LGBeamSilent = 1;
-                       p.draw = Draw_GrapplingHook;
-                       break;
-               case 3: // gauntlet beam
-                       p.GauntletBeamKillTime = time + 0.1;
-                       p.GauntletBeamStart = start;
-                       p.GauntletBeamEnd = end;
-                       p.GauntletBeamSilent = 0;
-                       p.draw = Draw_GrapplingHook;
-                       break;
+               self.draw = Draw_GrapplingHook;
+               self.entremove = Remove_GrapplingHook;
+
+               switch(self.HookType)
+               {
+                       default:
+                       case ENT_CLIENT_HOOK:
+                               // for the model
+                               setmodel(self, "models/hook.md3");
+                               self.drawmask = MASK_NORMAL;
+                               break;
+                       case ENT_CLIENT_LGBEAM:
+                               sound (self, CHAN_PROJECTILE, "weapons/lgbeam_fly.wav", VOL_BASE, ATTN_NORM);
+                               break;
+                       case ENT_CLIENT_GAUNTLET:
+                               sound (self, CHAN_PROJECTILE, "weapons/gauntletbeam_fly.wav", VOL_BASE, ATTN_NORM);
+                               break;
+               }
        }
 }
 
@@ -270,4 +233,7 @@ void Hook_Precache()
 {
        precache_sound("weapons/lgbeam_fly.wav");
        precache_sound("weapons/gauntletbeam_fly.wav");
+       precache_model("models/hook.md3");
 }
+
+// TODO: hook: temporarily transform self.origin for drawing the model along warpzones!
index e8a20f55681ed45cb6991b98a03848f0f2ea9085..7a5cf3447b1a29d502953812c276d533bb8fbb78 100644 (file)
@@ -59,7 +59,6 @@ const float TE_CSQC_ANNOUNCE = 110;
 const float TE_CSQC_TARGET_MUSIC = 111;
 const float TE_CSQC_NOTIFY = 112;
 const float TE_CSQC_WEAPONCOMPLAIN = 113;
-const float TE_CSQC_BEAM = 114;
 const float TE_CSQC_CAMPINGRIFLE_SCOPE = 115;
 
 const float RACE_NET_CHECKPOINT_HIT_QUALIFYING = 0; // byte checkpoint, short time, short recordtime, string recordholder
@@ -107,6 +106,9 @@ const float ENT_CLIENT_TUBANOTE = 23;
 const float ENT_CLIENT_WARPZONE = 24;
 const float ENT_CLIENT_WARPZONE_CAMERA = 25;
 const float ENT_CLIENT_TRIGGER_MUSIC = 26;
+const float ENT_CLIENT_HOOK = 27;
+const float ENT_CLIENT_LGBEAM = 28;
+const float ENT_CLIENT_GAUNTLET = 29;
 
 const float ENT_CLIENT_TURRET = 40;
 
index 3ce0b310dc5ada3aab5d2c7e4bffb650de062476..ce3d9b72b8fe9db1f484e27ac4bc620d90bd3b53 100644 (file)
@@ -114,6 +114,33 @@ void GrapplingHook_Stop()
        self.hook_length = -1;
 }
 
+.vector hook_start, hook_end;
+float GrapplingHookSend(entity to, float sf)
+{
+       WriteByte(MSG_ENTITY, ENT_CLIENT_HOOK);
+       sf = sf & 0x7F;
+       if(sound_allowed(MSG_BROADCAST, self.owner))
+               sf |= 0x80;
+       WriteByte(MSG_ENTITY, sf);
+       if(sf & 1)
+       {
+               WriteByte(MSG_ENTITY, num_for_edict(self.owner));
+       }
+       if(sf & 2)
+       {
+               WriteCoord(MSG_ENTITY, self.hook_start_x);
+               WriteCoord(MSG_ENTITY, self.hook_start_y);
+               WriteCoord(MSG_ENTITY, self.hook_start_z);
+       }
+       if(sf & 4)
+       {
+               WriteCoord(MSG_ENTITY, self.hook_end_x);
+               WriteCoord(MSG_ENTITY, self.hook_end_y);
+               WriteCoord(MSG_ENTITY, self.hook_end_z);
+       }
+       return TRUE;
+}
+
 void GrapplingHookThink()
 {
        float spd, dist, minlength, pullspeed, ropestretch, ropeairfriction, rubberforce, newlength, rubberforce_overstretch, s;
@@ -243,17 +270,16 @@ void GrapplingHookThink()
        makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0');
        myorg = WarpZone_RefSys_TransformOrigin(self, self.owner, self.origin) + v_forward * (-9);
 
-       // TODO turn into a csqc entity
-       WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
-       WriteByte(MSG_BROADCAST, TE_CSQC_BEAM);
-       WriteByte(MSG_BROADCAST, num_for_edict(self.owner));
-       WriteByte(MSG_BROADCAST, 0);
-       WriteCoord(MSG_BROADCAST, myorg_x);
-       WriteCoord(MSG_BROADCAST, myorg_y);
-       WriteCoord(MSG_BROADCAST, myorg_z);
-       WriteCoord(MSG_BROADCAST, org_x);
-       WriteCoord(MSG_BROADCAST, org_y);
-       WriteCoord(MSG_BROADCAST, org_z);
+       if(myorg != self.hook_start)
+       {
+               self.SendFlags |= 2;
+               self.hook_start = myorg;
+       }
+       if(org != self.hook_end)
+       {
+               self.SendFlags |= 4;
+               self.hook_end = org;
+       }
 }
 
 void GrapplingHookTouch (void)
@@ -327,7 +353,7 @@ void FireGrapplingHook (void)
        missile.movetype = MOVETYPE_FLY;
        PROJECTILE_MAKETRIGGER(missile);
 
-       setmodel (missile, "models/hook.md3"); // precision set below
+       //setmodel (missile, "models/hook.md3"); // precision set below
        setsize (missile, '-3 -3 -3', '3 3 3');
        setorigin (missile, org);
 
@@ -348,6 +374,10 @@ void FireGrapplingHook (void)
        missile.event_damage = GrapplingHook_Damage;
        missile.takedamage = DAMAGE_AIM;
        missile.damageforcescale = 0;
+
+       missile.hook_start = missile.hook_end = missile.origin;
+
+       Net_LinkEntity(missile, FALSE, 0, GrapplingHookSend);
 }
 
 //  void GrapplingHookFrame()
index 3a54cf3861945c882b30ba5e3afcb77cb817e8d8..c569b2f11ca344f4c87afa0e8fa93502836bf691 100644 (file)
@@ -184,6 +184,32 @@ void W_Electro_Attack2()
        CSQCProjectile(proj, TRUE, PROJECTILE_ELECTRO, FALSE); // no culling, it has sound
 }
 
+.vector hook_start, hook_end;
+float lgbeam_send(entity to, float sf)
+{
+       WriteByte(MSG_ENTITY, ENT_CLIENT_LGBEAM);
+       sf = sf & 0x7F;
+       if(sound_allowed(MSG_BROADCAST, self.owner))
+               sf |= 0x80;
+       WriteByte(MSG_ENTITY, sf);
+       if(sf & 1)
+       {
+               WriteByte(MSG_ENTITY, num_for_edict(self.owner));
+       }
+       if(sf & 2)
+       {
+               WriteCoord(MSG_ENTITY, self.hook_start_x);
+               WriteCoord(MSG_ENTITY, self.hook_start_y);
+               WriteCoord(MSG_ENTITY, self.hook_start_z);
+       }
+       if(sf & 4)
+       {
+               WriteCoord(MSG_ENTITY, self.hook_end_x);
+               WriteCoord(MSG_ENTITY, self.hook_end_y);
+               WriteCoord(MSG_ENTITY, self.hook_end_z);
+       }
+       return TRUE;
+}
 .entity lgbeam;
 .float prevlgfire;
 void lgbeam_think()
@@ -226,17 +252,16 @@ void lgbeam_think()
                vecs = '0 0 0';
        org = self.owner.origin + self.owner.view_ofs + v_forward * vecs_x + v_right * vecs_y + v_up * vecs_z;
        
-       // TODO turn into a csqc entity
-       WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
-       WriteByte(MSG_BROADCAST, TE_CSQC_BEAM);
-       WriteByte(MSG_BROADCAST, num_for_edict(self.owner));
-       WriteByte(MSG_BROADCAST, sound_allowed(MSG_BROADCAST, self.owner) ? 1 : 2);
-       WriteCoord(MSG_BROADCAST, trace_endpos_x);
-       WriteCoord(MSG_BROADCAST, trace_endpos_y);
-       WriteCoord(MSG_BROADCAST, trace_endpos_z);
-       WriteCoord(MSG_BROADCAST, org_x);
-       WriteCoord(MSG_BROADCAST, org_y);
-       WriteCoord(MSG_BROADCAST, org_z);
+       if(org != self.hook_start)
+       {
+               self.SendFlags |= 2;
+               self.hook_start = org;
+       }
+       if(trace_endpos != self.hook_end)
+       {
+               self.SendFlags |= 4;
+               self.hook_end = trace_endpos;
+       }
 }
 
 // experimental lightning gun
@@ -258,6 +283,7 @@ void W_Electro_Attack3 (void)
        beam.shot_spread = 0;
        beam.bot_dodge = TRUE;
        beam.bot_dodgerating = cvar("g_balance_electro_primary_damage");
+       Net_LinkEntity(beam, FALSE, 0, lgbeam_send);
 
        oldself = self;
        self = beam;
index 8633297a12d2c31bbbfe16d77b3b5d79a571322d..dd5e30ed8168d0ef625746f19b7bcfb758e88560 100644 (file)
@@ -101,6 +101,32 @@ void W_Laser_Attack (float issecondary)
        }
 }
 
+.vector hook_start, hook_end;
+float gauntletbeam_send(entity to, float sf)
+{
+       WriteByte(MSG_ENTITY, ENT_CLIENT_GAUNTLET);
+       sf = sf & 0x7F;
+       if(sound_allowed(MSG_BROADCAST, self.owner))
+               sf |= 0x80;
+       WriteByte(MSG_ENTITY, sf);
+       if(sf & 1)
+       {
+               WriteByte(MSG_ENTITY, num_for_edict(self.owner));
+       }
+       if(sf & 2)
+       {
+               WriteCoord(MSG_ENTITY, self.hook_start_x);
+               WriteCoord(MSG_ENTITY, self.hook_start_y);
+               WriteCoord(MSG_ENTITY, self.hook_start_z);
+       }
+       if(sf & 4)
+       {
+               WriteCoord(MSG_ENTITY, self.hook_end_x);
+               WriteCoord(MSG_ENTITY, self.hook_end_y);
+               WriteCoord(MSG_ENTITY, self.hook_end_z);
+       }
+       return TRUE;
+}
 .entity gauntletbeam;
 .float prevgauntletfire;
 void gauntletbeam_think()
@@ -153,17 +179,16 @@ void gauntletbeam_think()
                vecs = '0 0 0';
        org = self.owner.origin + self.owner.view_ofs + v_forward * vecs_x + v_right * vecs_y + v_up * vecs_z;
        
-       // TODO turn into a csqc entity
-       WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
-       WriteByte(MSG_BROADCAST, TE_CSQC_BEAM);
-       WriteByte(MSG_BROADCAST, num_for_edict(self.owner));
-       WriteByte(MSG_BROADCAST, 3);
-       WriteCoord(MSG_BROADCAST, trace_endpos_x);
-       WriteCoord(MSG_BROADCAST, trace_endpos_y);
-       WriteCoord(MSG_BROADCAST, trace_endpos_z);
-       WriteCoord(MSG_BROADCAST, org_x);
-       WriteCoord(MSG_BROADCAST, org_y);
-       WriteCoord(MSG_BROADCAST, org_z);
+       if(org != self.hook_start)
+       {
+               self.SendFlags |= 2;
+               self.hook_start = org;
+       }
+       if(trace_endpos != self.hook_end)
+       {
+               self.SendFlags |= 4;
+               self.hook_end = trace_endpos;
+       }
 }
 
 // experimental gauntlet
@@ -186,6 +211,7 @@ void W_Laser_Attack2 (float issecondary)
        beam.bot_dodge = TRUE;
        beam.bot_dodgerating = cvar("g_balance_laser_primary_damage");
        beam.cnt = issecondary;
+       Net_LinkEntity(beam, FALSE, 0, gauntletbeam_send);
 
        oldself = self;
        self = beam;