]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/w_laser.qc
Working more on effects -- still can't get the damn circular effect to work properly...
[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, w_shotorg, 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
201         missile.think = W_Laser_Think;
202         missile.nextthink = time + autocvar_g_balance_laser_primary_delay;
203
204         other = missile; MUTATOR_CALLHOOK(EditProjectile);
205
206         if(time >= missile.nextthink)
207         {
208                 entity oldself;
209                 oldself = self;
210                 self = missile;
211                 self.think();
212                 self = oldself;
213         }
214 }
215
216 .vector hook_start, hook_end;
217 float gauntletbeam_send(entity to, float sf)
218 {
219         WriteByte(MSG_ENTITY, ENT_CLIENT_GAUNTLET);
220         sf = sf & 0x7F;
221         if(sound_allowed(MSG_BROADCAST, self.realowner))
222                 sf |= 0x80;
223         WriteByte(MSG_ENTITY, sf);
224         if(sf & 1)
225         {
226                 WriteByte(MSG_ENTITY, num_for_edict(self.realowner));
227         }
228         if(sf & 2)
229         {
230                 WriteCoord(MSG_ENTITY, self.hook_start_x);
231                 WriteCoord(MSG_ENTITY, self.hook_start_y);
232                 WriteCoord(MSG_ENTITY, self.hook_start_z);
233         }
234         if(sf & 4)
235         {
236                 WriteCoord(MSG_ENTITY, self.hook_end_x);
237                 WriteCoord(MSG_ENTITY, self.hook_end_y);
238                 WriteCoord(MSG_ENTITY, self.hook_end_z);
239         }
240         return TRUE;
241 }
242 .entity gauntletbeam;
243 .float prevgauntletfire;
244 entity lgbeam_owner_ent;
245 void gauntletbeam_think()
246 {
247         float damage, myforce, myradius;
248         damage = autocvar_g_balance_laser_secondary_damage;
249         myforce = autocvar_g_balance_laser_secondary_force;
250         myradius = autocvar_g_balance_laser_secondary_radius;
251
252         self.realowner.prevgauntletfire = time;
253         if (self.realowner.weaponentity.state != WS_INUSE || self != self.realowner.gauntletbeam || self.realowner.deadflag != DEAD_NO || !self.realowner.BUTTON_ATCK2)
254         {
255                 remove(self);
256                 return;
257         }
258
259         self.nextthink = time;
260
261         makevectors(self.realowner.v_angle);
262
263         float dt;
264         dt = frametime;
265
266         W_SetupShot_Range(self.realowner, TRUE, 0, "", 0, damage * dt, myradius);
267         if(!lgbeam_owner_ent)
268         {
269                 lgbeam_owner_ent = spawn();
270                 lgbeam_owner_ent.classname = "lgbeam_owner_ent";
271         }
272         WarpZone_traceline_antilag(lgbeam_owner_ent, w_shotorg, w_shotend, MOVE_NORMAL, lgbeam_owner_ent, ANTILAG_LATENCY(self.owner));
273
274         // apply the damage
275         if(trace_ent)
276         {
277                 vector force;
278                 force = w_shotdir * myforce;
279                 if(accuracy_isgooddamage(self.owner, trace_ent))
280                         accuracy_add(self.owner, WEP_LASER, 0, damage * dt);
281                 Damage (trace_ent, self.owner, self.owner, damage * dt, WEP_LASER | HITTYPE_SECONDARY, trace_endpos, force * dt);
282         }
283
284         // draw effect
285         if(w_shotorg != self.hook_start)
286         {
287                 self.SendFlags |= 2;
288                 self.hook_start = w_shotorg;
289         }
290         if(w_shotend != self.hook_end)
291         {
292                 self.SendFlags |= 4;
293                 self.hook_end = w_shotend;
294         }
295 }
296
297 // experimental gauntlet
298 void W_Laser_Attack2 ()
299 {
300         // only play fire sound if 0.5 sec has passed since player let go the fire button
301         if(time - self.prevgauntletfire > 0.5)
302         {
303                 sound (self, CH_WEAPON_A, "weapons/gauntlet_fire.wav", VOL_BASE, ATTN_NORM);
304         }
305
306         entity beam, oldself;
307
308         self.gauntletbeam = beam = spawn();
309         beam.solid = SOLID_NOT;
310         beam.think = gauntletbeam_think;
311         beam.owner = self;
312         beam.movetype = MOVETYPE_NONE;
313         beam.shot_spread = 0;
314         beam.bot_dodge = TRUE;
315         beam.bot_dodgerating = autocvar_g_balance_laser_primary_damage;
316         Net_LinkEntity(beam, FALSE, 0, gauntletbeam_send);
317
318         oldself = self;
319         self = beam;
320         self.think();
321         self = oldself;
322 }
323
324 void LaserInit()
325 {
326         weapon_action(WEP_LASER, WR_PRECACHE);
327         gauntlet_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LASER), FALSE, FALSE, 1);
328         gauntlet_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LASER), FALSE, FALSE, 2);
329         gauntlet_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LASER), FALSE, FALSE, 3);
330         gauntlet_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_LASER), FALSE, FALSE, 4);
331 }
332
333 void spawnfunc_weapon_laser (void)
334 {
335         weapon_defaultspawnfunc(WEP_LASER);
336 }
337
338 float w_laser(float req)
339 {
340         float r1;
341         float r2;
342         if (req == WR_AIM)
343         {
344                 if(autocvar_g_balance_laser_secondary)
345                 {
346                         r1 = autocvar_g_balance_laser_primary_damage;
347                         r2 = autocvar_g_balance_laser_secondary_damage;
348                         if (random() * (r2 + r1) > r1)
349                                 self.BUTTON_ATCK2 = bot_aim(autocvar_g_balance_laser_secondary_speed, 0, autocvar_g_balance_laser_secondary_lifetime, FALSE);
350                         else
351                                 self.BUTTON_ATCK = bot_aim(autocvar_g_balance_laser_primary_speed, 0, autocvar_g_balance_laser_primary_lifetime, FALSE);
352                 }
353                 else
354                         self.BUTTON_ATCK = bot_aim(autocvar_g_balance_laser_primary_speed, 0, autocvar_g_balance_laser_primary_lifetime, FALSE);
355         }
356         else if (req == WR_THINK)
357         {
358                 if(autocvar_g_balance_laser_reload_ammo && self.clip_load < 1) // forced reload
359                         weapon_action(self.weapon, WR_RELOAD);
360                 else if (self.BUTTON_ATCK)
361                 {
362                         if (weapon_prepareattack(0, autocvar_g_balance_laser_primary_refire))
363                         {
364                                 W_DecreaseAmmo(ammo_none, 1, TRUE);
365
366                                 W_Laser_Shockwave();
367                                 weapon_thinkf(WFRAME_FIRE1, autocvar_g_balance_laser_primary_animtime, w_ready);
368                         }
369                 }
370                 else if (self.BUTTON_ATCK2)
371                 {
372                         if(autocvar_g_balance_laser_secondary)
373                         {
374                                 W_DecreaseAmmo(ammo_none, 1, TRUE);
375
376                                 if (weapon_prepareattack(0, 0))
377                                 {
378                                         W_Laser_Attack2();
379                                         weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_laser_secondary_animtime, w_ready);
380                                 }
381                         }
382                         else
383                         {
384                                 if(self.switchweapon == WEP_LASER) // don't do this if already switching
385                                         W_LastWeapon();
386                         }
387                 }
388         }
389         else if (req == WR_PRECACHE)
390         {
391                 precache_model ("models/weapons/g_laser.md3");
392                 precache_model ("models/weapons/v_laser.md3");
393                 precache_model ("models/weapons/h_laser.iqm");
394                 precache_sound ("weapons/lasergun_fire.wav");
395                 precache_sound ("weapons/gauntlet_fire.wav");
396                 //precache_sound ("weapons/reload.wav"); // until weapons have individual reload sounds, precache the reload sound somewhere else
397         }
398         else if (req == WR_SETUP)
399         {
400                 weapon_setup(WEP_LASER);
401                 self.current_ammo = ammo_none;
402         }
403         else if (req == WR_CHECKAMMO1)
404         {
405                 return TRUE;
406         }
407         else if (req == WR_CHECKAMMO2)
408         {
409                 return TRUE;
410         }
411         else if (req == WR_RELOAD)
412         {
413                 W_Reload(0, autocvar_g_balance_laser_reload_ammo, autocvar_g_balance_laser_reload_time, "weapons/reload.wav");
414         }
415         return TRUE;
416 }
417 #endif
418 #ifdef CSQC
419 float w_laser(float req)
420 {
421         if(req == WR_IMPACTEFFECT)
422         {
423                 vector org2;
424                 org2 = w_org + w_backoff * 6;
425                 pointparticles(particleeffectnum("new_laser_impact"), org2, w_backoff * 1000, 1);
426                 if(!w_issilent)
427                         sound(self, CH_SHOTS, "weapons/laserimpact.wav", VOL_BASE, ATTN_NORM);
428         }
429         else if(req == WR_PRECACHE)
430         {
431                 precache_sound("weapons/laserimpact.wav");
432         }
433         else if (req == WR_SUICIDEMESSAGE)
434                 w_deathtypestring = _("%s lasered themself to hell");
435         else if (req == WR_KILLMESSAGE)
436         {
437                 if(w_deathtype & HITTYPE_SECONDARY)
438                         w_deathtypestring = _("%s was cut in half by %s's gauntlet"); // unchecked: SPLASH
439                 else
440                         w_deathtypestring = _("%s was lasered to death by %s"); // unchecked: SPLASH
441         }
442         return TRUE;
443 }
444 #endif
445 #endif