Merge remote-tracking branch 'origin/master' into samual/spawn_weapons
[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                                 W_Laser_Shockwave();
368                                 weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_laser_primary_animtime, w_ready);
369                         }
370                 }
371                 else if (self.BUTTON_ATCK2)
372                 {
373                         if(autocvar_g_balance_laser_secondary)
374                         {
375                                 W_DecreaseAmmo(ammo_none, 1, TRUE);
376
377                                 if (weapon_prepareattack(0, 0))
378                                 {
379                                         W_Laser_Attack2();
380                                         weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_laser_secondary_animtime, w_ready);
381                                 }
382                         }
383                         else
384                         {
385                                 if(self.switchweapon == WEP_LASER) // don't do this if already switching
386                                         W_LastWeapon();
387                         }
388                 }
389         }
390         else if (req == WR_PRECACHE)
391         {
392                 precache_model ("models/weapons/g_laser.md3");
393                 precache_model ("models/weapons/v_laser.md3");
394                 precache_model ("models/weapons/h_laser.iqm");
395                 precache_sound ("weapons/lasergun_fire.wav");
396                 precache_sound ("weapons/gauntlet_fire.wav");
397                 //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
398         }
399         else if (req == WR_SETUP)
400         {
401                 weapon_setup(WEP_LASER);
402                 self.current_ammo = ammo_none;
403         }
404         else if (req == WR_CHECKAMMO1)
405         {
406                 return TRUE;
407         }
408         else if (req == WR_CHECKAMMO2)
409         {
410                 return TRUE;
411         }
412         else if (req == WR_RELOAD)
413         {
414                 W_Reload(0, autocvar_g_balance_laser_reload_ammo, autocvar_g_balance_laser_reload_time, "weapons/reload.wav");
415         }
416         return TRUE;
417 }
418 #endif
419 #ifdef CSQC
420 float w_laser(float req)
421 {
422         if(req == WR_IMPACTEFFECT)
423         {
424                 vector org2;
425                 org2 = w_org + w_backoff * 6;
426                 pointparticles(particleeffectnum("new_laser_impact"), org2, w_backoff * 1000, 1);
427                 if(!w_issilent)
428                         sound(self, CH_SHOTS, "weapons/laserimpact.wav", VOL_BASE, ATTN_NORM);
429         }
430         else if(req == WR_PRECACHE)
431         {
432                 precache_sound("weapons/laserimpact.wav");
433         }
434         else if (req == WR_SUICIDEMESSAGE)
435                 w_deathtypestring = _("%s lasered themself to hell");
436         else if (req == WR_KILLMESSAGE)
437         {
438                 if(w_deathtype & HITTYPE_SECONDARY)
439                         w_deathtypestring = _("%s was cut in half by %s's gauntlet"); // unchecked: SPLASH
440                 else
441                         w_deathtypestring = _("%s was lasered to death by %s"); // unchecked: SPLASH
442         }
443         return TRUE;
444 }
445 #endif
446 #endif