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