]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/w_laser.qc
Add toggle option for old and new laser behavior
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / w_laser.qc
1 #ifdef REGISTER_WEAPON
2 REGISTER_WEAPON(LASER, w_laser, 0, 1, WEP_FLAG_NORMAL | WEP_FLAG_RELOADABLE | WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH, 0, "laser", "laser", _("Laser"))
3 #else
4 #ifdef SVQC
5 void(float imp) W_SwitchWeapon;
6 void() W_LastWeapon;
7
8 void SendCSQCShockwaveParticle(float spread, vector endpos) 
9 {
10         //WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos);
11         WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
12         WriteByte(MSG_BROADCAST, TE_CSQC_SHOCKWAVEPARTICLE);
13         WriteCoord(MSG_BROADCAST, w_shotorg_x);
14         WriteCoord(MSG_BROADCAST, w_shotorg_y);
15         WriteCoord(MSG_BROADCAST, w_shotorg_z);
16         WriteCoord(MSG_BROADCAST, endpos_x);
17         WriteCoord(MSG_BROADCAST, endpos_y);
18         WriteCoord(MSG_BROADCAST, endpos_z);
19         WriteByte(MSG_BROADCAST, bound(0, 255 * spread, 255));
20 }
21
22 void W_Laser_Touch (void)
23 {
24         PROJECTILE_TOUCH;
25
26         self.event_damage = SUB_Null;
27         if (self.dmg)
28                 RadiusDamage (self, self.realowner, autocvar_g_balance_laser_secondary_damage, autocvar_g_balance_laser_secondary_edgedamage, autocvar_g_balance_laser_secondary_radius, world, world, autocvar_g_balance_laser_secondary_force, self.projectiledeathtype, other);
29         else
30                 RadiusDamage (self, self.realowner, autocvar_g_balance_laser_primary_damage, autocvar_g_balance_laser_primary_edgedamage, autocvar_g_balance_laser_primary_radius, world, world, autocvar_g_balance_laser_primary_force, self.projectiledeathtype, other);
31
32         remove (self);
33 }
34
35 void W_Laser_Think()
36 {
37         self.movetype = MOVETYPE_FLY;
38         self.think = SUB_Remove;
39         if (self.dmg)
40                 self.nextthink = time + autocvar_g_balance_laser_secondary_lifetime;
41         else
42                 self.nextthink = time + autocvar_g_balance_laser_primary_lifetime;
43         CSQCProjectile(self, TRUE, PROJECTILE_LASER, TRUE);
44 }
45
46 void W_Laser_Shockwave (void)
47 {
48         // declarations
49         float final_damage, final_spread;
50         entity head, next, aim_ent;
51         vector nearest, attack_hitpos, angle_to_head, angle_to_attack, final_force, center;
52         
53         // set up the shot direction
54         vector wanted_shot_direction = (v_forward * cos(autocvar_g_balance_laser_primary_shotangle * DEG2RAD) + v_up * sin(autocvar_g_balance_laser_primary_shotangle * DEG2RAD));
55         W_SetupShot_Dir(self, wanted_shot_direction, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_balance_laser_primary_damage);
56         vector attack_endpos = (w_shotorg + (w_shotdir * autocvar_g_balance_laser_primary_radius));
57
58         // find out what we're pointing at
59         WarpZone_TraceLine(w_shotorg, attack_endpos, FALSE, self);
60         aim_ent = trace_ent;
61         attack_hitpos = trace_endpos;
62         
63         // do the jump explosion now (also handles the impact effect)
64         RadiusDamageForSource(self, trace_endpos, '0 0 0', self, autocvar_g_balance_laser_primary_damage, autocvar_g_balance_laser_primary_edgedamage, autocvar_g_balance_laser_primary_jumpradius, world, self, TRUE, autocvar_g_balance_laser_primary_force, WEP_LASER, world);
65         
66         // also do the firing effect now
67         SendCSQCShockwaveParticle(autocvar_g_balance_laser_primary_spread, attack_hitpos);
68         
69         // did we hit a player directly?
70         if(aim_ent.takedamage)
71         {
72                 // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
73                 if (aim_ent.classname == "player")
74                         center = aim_ent.origin + aim_ent.view_ofs;
75                 else
76                         center = aim_ent.origin + (aim_ent.mins + aim_ent.maxs) * 0.5;
77
78                 final_force = (normalize(center - attack_hitpos) * autocvar_g_balance_laser_primary_force);
79                 Damage(aim_ent, self, self, autocvar_g_balance_laser_primary_damage, WEP_LASER, aim_ent.origin, final_force);
80                 print("Player hit directly via aim!\n");
81         }
82
83         // now figure out if I hit anything else than what my aim directly pointed at...
84         head = WarpZone_FindRadius(w_shotorg, autocvar_g_balance_laser_primary_radius, FALSE);
85         while(head)
86         {
87                 next = head.chain;
88                 
89                 if((head != self && head != aim_ent) && (head.takedamage))
90                 {
91                         // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
92                         if (head.classname == "player")
93                                 center = head.origin + head.view_ofs;
94                         else
95                                 center = head.origin + (head.mins + head.maxs) * 0.5;
96
97                         // find the closest point on the enemy to the center of the attack
98                         float h = vlen(center - self.origin);
99                         float ang = acos(dotproduct(normalize(center - self.origin), w_shotdir));
100                         float a = h * cos(ang);
101                         
102                         // ang = angle between shotdir and h
103                         // h = hypotenuse, which is the distance between attacker to head
104                         // a = adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
105
106                         nearest = WarpZoneLib_NearestPointOnBox(center + head.mins, center + head.maxs, w_shotorg + a * w_shotdir);
107
108                         if(vlen(w_shotorg - nearest) <= autocvar_g_balance_laser_primary_radius)
109                         {
110                                 // is it within the limit of the spread?
111                                 nearest = head.WarpZone_findradius_nearest;
112                                 angle_to_head = normalize(nearest - w_shotorg);
113                                 angle_to_attack = w_shotdir;
114                                 final_spread = vlen(angle_to_head - angle_to_attack);
115                                 if(final_spread <= autocvar_g_balance_laser_primary_spread)
116                                 {
117                                         // TODO! we MUST check this, otherwise you can shoot through walls!
118                                         // just how to make sure that if a small part of the player is visible, we'll hit him?
119                                         // we can just do it the cheap way of tracing from shotorg to nearest, but what if there's an obstruction between those points, but the player still sees the enemy...?
120
121                                         // is it visible to the weapon?
122                                         //WarpZone_TraceLine(w_shotorg, nearest, MOVE_WORLDONLY, self);
123                                         //if(trace_fraction == 1)
124                                         //{
125                                                 // finally lets do some damage bitches!
126                                                 if(autocvar_g_balance_laser_primary_spread)
127                                                         final_damage = (final_spread / autocvar_g_balance_laser_primary_spread);
128                                                 else
129                                                         final_damage = 1;
130
131                                                 //final_force = (normalize(nearest - w_shotorg) * autocvar_g_balance_laser_primary_force); // we dont want to use nearest here, because that would result in some rather weird force dirs for the attacker...
132                                                 print(strcat("head.origin: ", vtos(head.origin), ", (w_shotorg + a * w_shotdir): ", vtos(w_shotorg + a * w_shotdir), ".\n"));
133                                                 print("a = ", ftos(a), " h = ", ftos(h), " ang = ", ftos(ang), "\n");
134                                                 final_force = (normalize(center - (w_shotorg + a * w_shotdir)) * autocvar_g_balance_laser_primary_force);
135                                                 final_damage = (autocvar_g_balance_laser_primary_damage * final_damage + autocvar_g_balance_laser_primary_edgedamage * (1 - final_damage));
136                                                 
137                                                 print(strcat("damage: ", ftos(final_damage), ", force: ", vtos(final_force), ".\n"));
138                                                 
139                                                 Damage(head, self, self, final_damage, WEP_LASER, head.origin, final_force);
140                                                 
141                                                 print(strcat(vtos(angle_to_head), " - ", vtos(angle_to_attack), ": ", ftos(vlen(angle_to_head - angle_to_attack)), ".\n"));
142                                                 //te_lightning2(world, nearest, w_shotorg);
143                                                 
144                                                 //pointparticles(particleeffectnum("rocket_guide"), w_shotorg, w_shotdir * 1000, 1);
145                                                 //SendCSQCShockwaveParticle(autocvar_g_balance_laser_primary_spread, trace_endpos);
146                                         //}
147                                 }
148                         }
149                 }
150                 head = next;
151         }
152 }
153
154 void W_Laser_Attack (float issecondary)
155 {
156         entity missile;
157         vector s_forward;
158         float a;
159         float nodamage;
160
161         if(issecondary == 2) // minstanex shot
162                 nodamage = g_minstagib;
163         else
164                 nodamage = FALSE;
165
166         a = autocvar_g_balance_laser_primary_shotangle;
167         s_forward = v_forward * cos(a * DEG2RAD) + v_up * sin(a * DEG2RAD);
168
169         if(nodamage)
170                 W_SetupShot_Dir (self, s_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, 0);
171         else if(issecondary == 1)
172                 W_SetupShot_Dir (self, s_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_balance_laser_secondary_damage);
173         else
174                 W_SetupShot_Dir (self, s_forward, FALSE, 3, "weapons/lasergun_fire.wav", CH_WEAPON_B, autocvar_g_balance_laser_primary_damage);
175         pointparticles(particleeffectnum("laser_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
176
177         missile = spawn ();
178         missile.owner = missile.realowner = self;
179         missile.classname = "laserbolt";
180         missile.dmg = 0;
181         if(!nodamage)
182         {
183                 missile.bot_dodge = TRUE;
184                 missile.bot_dodgerating = autocvar_g_balance_laser_primary_damage;
185         }
186
187         PROJECTILE_MAKETRIGGER(missile);
188         missile.projectiledeathtype = WEP_LASER;
189
190         setorigin (missile, w_shotorg);
191         setsize(missile, '0 0 0', '0 0 0');
192
193         W_SETUPPROJECTILEVELOCITY(missile, g_balance_laser_primary);
194         missile.angles = vectoangles (missile.velocity);
195         //missile.glow_color = 250; // 244, 250
196         //missile.glow_size = 120;
197         missile.touch = W_Laser_Touch;
198
199         missile.flags = FL_PROJECTILE;
200         missile.missile_flags = MIF_SPLASH; 
201
202         missile.think = W_Laser_Think;
203         missile.nextthink = time + autocvar_g_balance_laser_primary_delay;
204
205         other = missile; MUTATOR_CALLHOOK(EditProjectile);
206
207         if(time >= missile.nextthink)
208         {
209                 entity oldself;
210                 oldself = self;
211                 self = missile;
212                 self.think();
213                 self = oldself;
214         }
215 }
216
217 .vector hook_start, hook_end;
218 float gauntletbeam_send(entity to, float sf)
219 {
220         WriteByte(MSG_ENTITY, ENT_CLIENT_GAUNTLET);
221         sf = sf & 0x7F;
222         if(sound_allowed(MSG_BROADCAST, self.realowner))
223                 sf |= 0x80;
224         WriteByte(MSG_ENTITY, sf);
225         if(sf & 1)
226         {
227                 WriteByte(MSG_ENTITY, num_for_edict(self.realowner));
228         }
229         if(sf & 2)
230         {
231                 WriteCoord(MSG_ENTITY, self.hook_start_x);
232                 WriteCoord(MSG_ENTITY, self.hook_start_y);
233                 WriteCoord(MSG_ENTITY, self.hook_start_z);
234         }
235         if(sf & 4)
236         {
237                 WriteCoord(MSG_ENTITY, self.hook_end_x);
238                 WriteCoord(MSG_ENTITY, self.hook_end_y);
239                 WriteCoord(MSG_ENTITY, self.hook_end_z);
240         }
241         return TRUE;
242 }
243 .entity gauntletbeam;
244 .float prevgauntletfire;
245 entity lgbeam_owner_ent;
246 void gauntletbeam_think()
247 {
248         float damage, myforce, myradius;
249         damage = autocvar_g_balance_laser_secondary_damage;
250         myforce = autocvar_g_balance_laser_secondary_force;
251         myradius = autocvar_g_balance_laser_secondary_radius;
252
253         self.realowner.prevgauntletfire = time;
254         if (self.realowner.weaponentity.state != WS_INUSE || self != self.realowner.gauntletbeam || self.realowner.deadflag != DEAD_NO || !self.realowner.BUTTON_ATCK2)
255         {
256                 remove(self);
257                 return;
258         }
259
260         self.nextthink = time;
261
262         makevectors(self.realowner.v_angle);
263
264         float dt;
265         dt = frametime;
266
267         W_SetupShot_Range(self.realowner, TRUE, 0, "", 0, damage * dt, myradius);
268         if(!lgbeam_owner_ent)
269         {
270                 lgbeam_owner_ent = spawn();
271                 lgbeam_owner_ent.classname = "lgbeam_owner_ent";
272         }
273         WarpZone_traceline_antilag(lgbeam_owner_ent, w_shotorg, w_shotend, MOVE_NORMAL, lgbeam_owner_ent, ANTILAG_LATENCY(self.owner));
274
275         // apply the damage
276         if(trace_ent)
277         {
278                 vector force;
279                 force = w_shotdir * myforce;
280                 if(accuracy_isgooddamage(self.owner, trace_ent))
281                         accuracy_add(self.owner, WEP_LASER, 0, damage * dt);
282                 Damage (trace_ent, self.owner, self.owner, damage * dt, WEP_LASER | HITTYPE_SECONDARY, trace_endpos, force * dt);
283         }
284
285         // draw effect
286         if(w_shotorg != self.hook_start)
287         {
288                 self.SendFlags |= 2;
289                 self.hook_start = w_shotorg;
290         }
291         if(w_shotend != self.hook_end)
292         {
293                 self.SendFlags |= 4;
294                 self.hook_end = w_shotend;
295         }
296 }
297
298 // experimental gauntlet
299 void W_Laser_Attack2 ()
300 {
301         // only play fire sound if 0.5 sec has passed since player let go the fire button
302         if(time - self.prevgauntletfire > 0.5)
303         {
304                 sound (self, CH_WEAPON_A, "weapons/gauntlet_fire.wav", VOL_BASE, ATTN_NORM);
305         }
306
307         entity beam, oldself;
308
309         self.gauntletbeam = beam = spawn();
310         beam.solid = SOLID_NOT;
311         beam.think = gauntletbeam_think;
312         beam.owner = self;
313         beam.movetype = MOVETYPE_NONE;
314         beam.shot_spread = 0;
315         beam.bot_dodge = TRUE;
316         beam.bot_dodgerating = autocvar_g_balance_laser_primary_damage;
317         Net_LinkEntity(beam, FALSE, 0, gauntletbeam_send);
318
319         oldself = self;
320         self = beam;
321         self.think();
322         self = oldself;
323 }
324
325 void LaserInit()
326 {
327         weapon_action(WEP_LASER, WR_PRECACHE);
328         gauntlet_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LASER), FALSE, FALSE, 1);
329         gauntlet_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LASER), FALSE, FALSE, 2);
330         gauntlet_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LASER), FALSE, FALSE, 3);
331         gauntlet_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LASER), FALSE, FALSE, 4);
332 }
333
334 void spawnfunc_weapon_laser (void)
335 {
336         weapon_defaultspawnfunc(WEP_LASER);
337 }
338
339 float w_laser(float req)
340 {
341         float r1;
342         float r2;
343         if (req == WR_AIM)
344         {
345                 if(autocvar_g_balance_laser_secondary)
346                 {
347                         r1 = autocvar_g_balance_laser_primary_damage;
348                         r2 = autocvar_g_balance_laser_secondary_damage;
349                         if (random() * (r2 + r1) > r1)
350                                 self.BUTTON_ATCK2 = bot_aim(autocvar_g_balance_laser_secondary_speed, 0, autocvar_g_balance_laser_secondary_lifetime, FALSE);
351                         else
352                                 self.BUTTON_ATCK = bot_aim(autocvar_g_balance_laser_primary_speed, 0, autocvar_g_balance_laser_primary_lifetime, FALSE);
353                 }
354                 else
355                         self.BUTTON_ATCK = bot_aim(autocvar_g_balance_laser_primary_speed, 0, autocvar_g_balance_laser_primary_lifetime, FALSE);
356         }
357         else if (req == WR_THINK)
358         {
359                 if(autocvar_g_balance_laser_reload_ammo && self.clip_load < 1) // forced reload
360                         weapon_action(self.weapon, WR_RELOAD);
361                 else if (self.BUTTON_ATCK)
362                 {
363                         if (weapon_prepareattack(0, autocvar_g_balance_laser_primary_refire))
364                         {
365                                 W_DecreaseAmmo(ammo_none, 1, TRUE);
366
367
368                                 if not(autocvar_g_balance_laser_oldprimary)
369                                         W_Laser_Shockwave();
370                                 else
371                                         W_Laser_Attack(FALSE);
372
373                                 weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_laser_primary_animtime, w_ready);
374                         }
375                 }
376                 else if (self.BUTTON_ATCK2)
377                 {
378                         switch(autocvar_g_balance_laser_secondary)
379                         {
380                                 case 0: // switch to last used weapon
381                                 {
382                                         if(self.switchweapon == WEP_LASER) // don't do this if already switching
383                                                 W_LastWeapon();
384
385                                         break;
386                                 }
387
388                                 case 1: // normal projectile secondary
389                                 {
390                                         if(weapon_prepareattack(0, autocvar_g_balance_laser_secondary_refire))
391                                         {
392                                                 W_DecreaseAmmo(ammo_none, 1, TRUE);
393                                                 W_Laser_Attack(TRUE);
394                                                 weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_laser_secondary_animtime, w_ready);
395                                         }
396
397                                         break;
398                                 }
399
400                                 case 2: // gauntlet secondary
401                                 {
402                                         W_DecreaseAmmo(ammo_none, 1, TRUE);
403
404                                         if (weapon_prepareattack(0, 0))
405                                         {
406                                                 W_Laser_Attack2();
407                                                 weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_laser_secondary_animtime, w_ready);
408                                         }
409
410                                         break;
411                                 }
412                         }
413                 }
414         }
415         else if (req == WR_PRECACHE)
416         {
417                 precache_model ("models/weapons/g_laser.md3");
418                 precache_model ("models/weapons/v_laser.md3");
419                 precache_model ("models/weapons/h_laser.iqm");
420                 precache_sound ("weapons/lasergun_fire.wav");
421                 precache_sound ("weapons/gauntlet_fire.wav");
422                 //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
423         }
424         else if (req == WR_SETUP)
425         {
426                 weapon_setup(WEP_LASER);
427                 self.current_ammo = ammo_none;
428         }
429         else if (req == WR_CHECKAMMO1)
430         {
431                 return TRUE;
432         }
433         else if (req == WR_CHECKAMMO2)
434         {
435                 return TRUE;
436         }
437         else if (req == WR_RELOAD)
438         {
439                 W_Reload(0, autocvar_g_balance_laser_reload_ammo, autocvar_g_balance_laser_reload_time, "weapons/reload.wav");
440         }
441         return TRUE;
442 }
443 #endif
444 #ifdef CSQC
445 float w_laser(float req)
446 {
447         if(req == WR_IMPACTEFFECT)
448         {
449                 vector org2;
450                 org2 = w_org + w_backoff * 6;
451                 pointparticles(particleeffectnum("new_laser_impact"), org2, w_backoff * 1000, 1);
452                 if(!w_issilent)
453                         sound(self, CH_SHOTS, "weapons/laserimpact.wav", VOL_BASE, ATTN_NORM);
454         }
455         else if(req == WR_PRECACHE)
456         {
457                 precache_sound("weapons/laserimpact.wav");
458         }
459         else if (req == WR_SUICIDEMESSAGE)
460                 w_deathtypestring = _("%s lasered themself to hell");
461         else if (req == WR_KILLMESSAGE)
462         {
463                 if(w_deathtype & HITTYPE_SECONDARY)
464                         w_deathtypestring = _("%s was cut in half by %s's gauntlet"); // unchecked: SPLASH
465                 else
466                         w_deathtypestring = _("%s was lasered to death by %s"); // unchecked: SPLASH
467         }
468         return TRUE;
469 }
470 #endif
471 #endif