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