]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/weapons/w_arc.qc
759eafa99328401d96248e74ec852dc457623dba
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / weapons / w_arc.qc
1 #ifdef REGISTER_WEAPON
2 REGISTER_WEAPON(
3 /* WEP_##id  */ ARC,
4 /* function  */ W_Arc,
5 /* ammotype  */ ammo_cells,
6 /* impulse   */ 3,
7 /* flags     */ WEP_FLAG_NORMAL,
8 /* rating    */ BOT_PICKUP_RATING_HIGH,
9 /* color     */ '1 1 1',
10 /* modelname */ "hlac",
11 /* simplemdl */ "foobar",
12 /* crosshair */ "gfx/crosshairhlac 0.7",
13 /* wepimg    */ "weaponhlac",
14 /* refname   */ "arc",
15 /* wepname   */ _("Arc")
16 );
17
18 #define ARC_SETTINGS(w_cvar,w_prop) ARC_SETTINGS_LIST(w_cvar, w_prop, ARC, arc)
19 #define ARC_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
20         w_cvar(id, sn, NONE, beam_ammo) \
21         w_cvar(id, sn, NONE, beam_animtime) \
22         w_cvar(id, sn, NONE, beam_botaimspeed) \
23         w_cvar(id, sn, NONE, beam_botaimlifetime) \
24         w_cvar(id, sn, NONE, beam_damage) \
25         w_cvar(id, sn, NONE, beam_degreespersegment) \
26         w_cvar(id, sn, NONE, beam_distancepersegment) \
27         w_cvar(id, sn, NONE, beam_falloff_halflifedist) \
28         w_cvar(id, sn, NONE, beam_falloff_maxdist) \
29         w_cvar(id, sn, NONE, beam_falloff_mindist) \
30         w_cvar(id, sn, NONE, beam_force) \
31         w_cvar(id, sn, NONE, beam_maxangle) \
32         w_cvar(id, sn, NONE, beam_nonplayerdamage) \
33         w_cvar(id, sn, NONE, beam_range) \
34         w_cvar(id, sn, NONE, beam_refire) \
35         w_cvar(id, sn, NONE, beam_returnspeed) \
36         w_prop(id, sn, float,  switchdelay_raise, switchdelay_raise) \
37         w_prop(id, sn, float,  switchdelay_drop, switchdelay_drop) \
38         w_prop(id, sn, string, weaponreplace, weaponreplace) \
39         w_prop(id, sn, float,  weaponstart, weaponstart) \
40         w_prop(id, sn, float,  weaponstartoverride, weaponstartoverride) \
41         w_prop(id, sn, float,  weaponthrowable, weaponthrowable)
42
43 #ifndef MENUQC
44 vector arc_shotorigin[4];
45 .vector beam_start;
46 .vector beam_dir;
47 .vector beam_wantdir;
48 .float beam_type;
49 #define ARC_BT_MISS        0
50 #define ARC_BT_WALL        1
51 #define ARC_BT_HEAL        2
52 #define ARC_BT_HIT         3
53 #define ARC_BT_BURST_MISS  10
54 #define ARC_BT_BURST_WALL  11
55 #define ARC_BT_BURST_HEAL  12
56 #define ARC_BT_BURST_HIT   13
57 #define ARC_BT_BURSTMASK   10
58 #endif
59 #ifdef SVQC
60 #define ARC_MAX_SEGMENTS 20
61 ARC_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
62 void ArcInit(void);
63 .entity arc_beam; // used for beam
64 .float BUTTON_ATCK_prev; // for better animation control
65 .float lg_fire_prev; // for better animation control
66 #ifdef ARC_DEBUG
67 .entity lg_ents[ARC_MAX_SEGMENTS]; // debug
68 #endif
69 .float beam_initialized;
70 #endif
71 #else
72 #ifdef SVQC
73 void spawnfunc_weapon_arc(void) { weapon_defaultspawnfunc(WEP_ARC); }
74
75 float W_Arc_Beam_Send(entity to, float sf)
76 {
77         WriteByte(MSG_ENTITY, ENT_CLIENT_ARC_BEAM);
78
79         // don't send group 1, 2, or 3 if this beam is for the local player
80         if(to == self.owner) { sf &= ~7; }
81         WriteByte(MSG_ENTITY, sf);
82         
83         if(sf & 1) // starting location // not sent if beam is for owner
84         {
85                 //WriteByte(MSG_ENTITY, num_for_edict(self.owner));
86                 WriteCoord(MSG_ENTITY, self.beam_start_x);
87                 WriteCoord(MSG_ENTITY, self.beam_start_y);
88                 WriteCoord(MSG_ENTITY, self.beam_start_z);
89                 //WriteCoord(MSG_ENTITY, WEP_CVAR(arc, beam_range));
90         }
91         if(sf & 2) // want/aim direction
92         {
93                 WriteCoord(MSG_ENTITY, self.beam_wantdir_x);
94                 WriteCoord(MSG_ENTITY, self.beam_wantdir_y);
95                 WriteCoord(MSG_ENTITY, self.beam_wantdir_z);
96         }
97         if(sf & 4) // beam direction
98         {
99                 WriteCoord(MSG_ENTITY, self.beam_dir_x);
100                 WriteCoord(MSG_ENTITY, self.beam_dir_y);
101                 WriteCoord(MSG_ENTITY, self.beam_dir_z);
102         }
103         if(sf & 8) // beam type
104         {
105                 WriteByte(MSG_ENTITY, self.beam_type);
106         }
107         return TRUE;
108 }
109
110 void W_Arc_Beam_Think(void)
111 {
112         float i, burst = TRUE;
113         if(self != self.owner.arc_beam)
114         {
115                 #ifdef ARC_DEBUG
116                 if(self.lg_ents[0])
117                 {
118                         for(i = 0; i < ARC_MAX_SEGMENTS; ++i)
119                                 remove(self.lg_ents[i]);
120                 }
121                 print("W_Arc_Beam_Think(): EXPIRING BEAM #1\n");
122                 #endif
123                 remove(self);
124                 return;
125         }
126         if((self.owner.WEP_AMMO(ARC) <= 0 && !(self.owner.items & IT_UNLIMITED_WEAPON_AMMO)) || self.owner.deadflag != DEAD_NO || !self.owner.BUTTON_ATCK || self.owner.freezetag_frozen)
127         {
128                 if(self == self.owner.arc_beam) { self.owner.arc_beam = world; } // is this needed? I thought this is changed to world when removed ANYWAY
129                 #ifdef ARC_DEBUG
130                 if(self.lg_ents[0])
131                 {
132                         for(i = 0; i < ARC_MAX_SEGMENTS; ++i)
133                                 remove(self.lg_ents[i]);
134                 }
135                 print("W_Arc_Beam_Think(): EXPIRING BEAM #2\n");
136                 #endif
137                 remove(self);
138                 return;
139         }
140
141         if(self.owner.BUTTON_ATCK2)
142         {
143                 burst = ARC_BT_BURSTMASK;
144         }
145
146         // decrease ammo // todo: support burst ammo
147         float dt = frametime;
148         if(!(self.owner.items & IT_UNLIMITED_WEAPON_AMMO))
149         {
150                 if(WEP_CVAR(arc, beam_ammo))
151                 {
152                         dt = min(dt, self.owner.WEP_AMMO(ARC) / WEP_CVAR(arc, beam_ammo));
153                         self.owner.WEP_AMMO(ARC) = max(0, self.owner.WEP_AMMO(ARC) - WEP_CVAR(arc, beam_ammo) * frametime);
154                 }
155         }
156
157         makevectors(self.owner.v_angle);
158
159         W_SetupShot_Range(self.owner, TRUE, 0, "", 0, WEP_CVAR(arc, beam_damage) * dt, WEP_CVAR(arc, beam_range));
160
161         // network information: shot origin and want/aim direction
162         if(self.beam_start != w_shotorg)
163         {
164                 self.SendFlags |= 1;
165                 self.beam_start = w_shotorg;
166         }
167         if(self.beam_wantdir != w_shotdir)
168         {
169                 self.SendFlags |= 2;
170                 self.beam_wantdir = w_shotdir;
171         }
172
173         if(!self.beam_initialized)
174         {
175                 #ifdef ARC_DEBUG
176                 for(i = 0; i < ARC_MAX_SEGMENTS; ++i)
177                         self.lg_ents[i] = spawn();
178                 #endif
179                 
180                 self.beam_dir = w_shotdir;
181                 self.beam_initialized = TRUE;
182         }
183
184         float segments; 
185         if(self.beam_dir != w_shotdir)
186         {
187                 float angle = ceil(vlen(w_shotdir - self.beam_dir) * RAD2DEG);
188                 float anglelimit;
189                 if(angle && (angle > WEP_CVAR(arc, beam_maxangle)))
190                 {
191                         // if the angle is greater than maxangle, force the blendfactor to make this the maximum factor
192                         anglelimit = min(WEP_CVAR(arc, beam_maxangle) / angle, 1);
193                 }
194                 else
195                 {
196                         // the radius is not too far yet, no worries :D
197                         anglelimit = 1;
198                 }
199
200                 // calculate how much we're going to move the end of the beam to the want position
201                 float blendfactor = bound(0, anglelimit * (1 - (WEP_CVAR(arc, beam_returnspeed) * dt)), 1);
202                 self.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (self.beam_dir * blendfactor));
203
204                 // todo: figure out a way so that blendfactor becomes 0 at some point,
205                 // currently self.beam_dir and w_shotdir never really become equal as there is no rounding/snapping point
206                 // printf("blendfactor = %f\n", blendfactor);
207
208                 // network information: beam direction
209                 self.SendFlags |= 4;
210
211                 // calculate how many segments are needed
212                 float max_allowed_segments;
213
214                 if(WEP_CVAR(arc, beam_distancepersegment))
215                         max_allowed_segments = min(ARC_MAX_SEGMENTS, 1 + (vlen(w_shotdir / WEP_CVAR(arc, beam_distancepersegment))));
216                 else
217                         max_allowed_segments = ARC_MAX_SEGMENTS;
218
219                 if(WEP_CVAR(arc, beam_degreespersegment))
220                 {
221                         segments = min( max(1, ( min(angle, WEP_CVAR(arc, beam_maxangle)) / WEP_CVAR(arc, beam_degreespersegment) ) ), max_allowed_segments );
222                 }
223                 else { segments = 1; }
224         }
225         else
226         {
227                 segments = 1;
228         }
229
230         vector beam_endpos_estimate = (w_shotorg + (self.beam_dir * WEP_CVAR(arc, beam_range)));
231
232         #ifdef ARC_DEBUG
233         //printf("segment count: %d\n", segments);
234         //string segmentinfo = "";
235         #if 0
236         float totaldist = 0;
237         #endif
238         #endif
239
240         float new_beam_type = 0;
241         vector last_origin = w_shotorg;
242         for(i = 1; i <= segments; ++i)
243         {
244                 // calculate this on every segment to ensure that we always reach the full length of the attack
245                 float segmentblend = (i/segments);
246                 float segmentdist = vlen(beam_endpos_estimate - last_origin) * (i/segments);
247
248                 vector new_dir = normalize( (w_shotdir * (1 - segmentblend)) + (normalize(beam_endpos_estimate - last_origin) * segmentblend) );
249                 vector new_origin = last_origin + (new_dir * segmentdist);
250
251                 WarpZone_traceline_antilag(
252                         self.owner,
253                         last_origin,
254                         new_origin,
255                         MOVE_NORMAL,
256                         self.owner,
257                         ANTILAG_LATENCY(self.owner)
258                 );
259
260                 #ifdef ARC_DEBUG
261                 //APPEND_TO_STRING(segmentinfo, "^4, ", sprintf("^3org%s-dis'%f'", vtos(new_origin), segmentdist));
262                 #endif
263
264                 float is_player = (trace_ent.classname == "player" || trace_ent.classname == "body" || (trace_ent.flags & FL_MONSTER));
265                 if(trace_ent && (trace_ent.takedamage == DAMAGE_AIM) && (is_player || WEP_CVAR(arc, beam_nonplayerdamage)))
266                 {
267                         // calculate our own hit origin as trace_endpos tends to jump around annoyingly (to player origin?)
268                         vector hitorigin = last_origin + (new_dir * segmentdist * trace_fraction);
269                         #ifdef ARC_DEBUG
270                         #if 0
271                         totaldist += segmentdist * trace_fraction;
272                         #endif
273                         #endif
274
275                         float falloff = ExponentialFalloff(
276                                 WEP_CVAR(arc, beam_falloff_mindist),
277                                 WEP_CVAR(arc, beam_falloff_maxdist),
278                                 WEP_CVAR(arc, beam_falloff_halflifedist),
279                                 vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, hitorigin) - w_shotorg)
280                         );
281
282                         if(is_player && SAME_TEAM(self.owner, trace_ent))
283                         {
284                                 // hit a team mate heal them now
285                                 #ifdef ARC_DEBUG
286                                 te_lightning1(self.lg_ents[i - 1], last_origin, hitorigin);
287                                 te_customflash(hitorigin, 80, 5, '0 1 0');
288                                 #if 0
289                                 printf(
290                                         "W_Arc_Beam_Think(): HIT TEAM MATE: "
291                                         "Hitorg: %s, Segments: %d, Distance: %f\n",
292                                         vtos(hitorigin),
293                                         i,
294                                         totaldist
295                                 );
296                                 #endif
297                                 #endif
298                                 new_beam_type = ARC_BT_HEAL;
299                         }
300                         else
301                         {
302                                 float rootdamage;
303                                 if(is_player)
304                                         rootdamage = WEP_CVAR(arc, beam_damage);
305                                 else
306                                         rootdamage = WEP_CVAR(arc, beam_nonplayerdamage);
307
308                                 if(accuracy_isgooddamage(self.owner, trace_ent))
309                                 {
310                                         accuracy_add(
311                                                 self.owner,
312                                                 WEP_ARC,
313                                                 0,
314                                                 rootdamage * dt * falloff
315                                         );
316                                 }
317
318                                 Damage(
319                                         trace_ent,
320                                         self.owner,
321                                         self.owner,
322                                         rootdamage * dt * falloff,
323                                         WEP_ARC,
324                                         hitorigin,
325                                         WEP_CVAR(arc, beam_force) * new_dir * dt * falloff
326                                 );
327
328                                 #ifdef ARC_DEBUG
329                                 te_lightning1(self.lg_ents[i - 1], last_origin, hitorigin);
330                                 te_customflash(hitorigin, 80, 5, '1 0 0');
331                                 #if 0
332                                 printf(
333                                         "W_Arc_Beam_Think(): HIT ENTITY: "
334                                         "Hitorg: %s, Segments: %d, Distance: %f\n",
335                                         vtos(hitorigin),
336                                         i,
337                                         totaldist
338                                 );
339                                 #endif
340                                 #endif
341                                 new_beam_type = ARC_BT_HIT;
342                         }
343                         break; 
344                 }
345                 else if(trace_fraction != 1)
346                 {
347                         // we collided with geometry
348                         #ifdef ARC_DEBUG
349                         te_lightning1(self.lg_ents[i - 1], last_origin, trace_endpos);
350                         te_customflash(trace_endpos, 50, 2, '0 0 1');
351                         #if 0
352                         totaldist += segmentdist * trace_fraction;
353                         printf(
354                                 "W_Arc_Beam_Think(): HIT WALL: "
355                                 "Hitorg: %s, Segments: %d, Distance: %f\n",
356                                 vtos(trace_endpos),
357                                 i,
358                                 totaldist
359                         );
360                         #endif
361                         #endif
362                         new_beam_type = ARC_BT_WALL;
363                         break;
364                 }
365                 else
366                 {
367                         #ifdef ARC_DEBUG
368                         #if 0
369                         totaldist += segmentdist;
370                         #endif
371                         te_lightning1(self.lg_ents[i - 1], last_origin, new_origin);
372                         #endif
373                         last_origin = new_origin;
374                 }
375         }
376
377         #ifdef ARC_DEBUG
378         if(!new_beam_type)
379         {
380                 #if 0
381                 printf(
382                         "W_Arc_Beam_Think(): MISS: "
383                         "Shotorg: %s, Endpos: %s, Segments: %d: %s\n",
384                         vtos(w_shotorg),
385                         vtos(trace_endpos),
386                         i,
387                         segmentinfo
388                 );
389                 #endif
390                 te_customflash(trace_endpos, 50, 2, '1 1 0');
391         }
392         #endif
393
394         // if we're bursting, use burst visual effects
395         new_beam_type += burst;
396
397         // network information: beam type
398         if(new_beam_type != self.beam_type)
399         {
400                 self.SendFlags |= 8;
401                 self.beam_type = new_beam_type;
402         }
403
404         self.owner.lg_fire_prev = time;
405         self.nextthink = time;
406 }
407
408 // Attack functions ========================= 
409 void W_Arc_Beam(void)
410 {
411         print("W_Arc_Beam();\n");
412         // only play fire sound if 1 sec has passed since player let go the fire button
413         if(time - self.lg_fire_prev > 1)
414                 sound(self, CH_WEAPON_A, "weapons/lgbeam_fire.wav", VOL_BASE, ATTN_NORM);
415
416         entity beam, oldself;
417
418         self.arc_beam = beam = spawn();
419         beam.classname = "W_Arc_Beam";
420         beam.solid = SOLID_NOT;
421         beam.think = W_Arc_Beam_Think;
422         beam.owner = self;
423         beam.movetype = MOVETYPE_NONE;
424         beam.shot_spread = 1;
425         beam.bot_dodge = TRUE;
426         beam.bot_dodgerating = WEP_CVAR(arc, beam_damage);
427         Net_LinkEntity(beam, FALSE, 0, W_Arc_Beam_Send);
428
429         oldself = self;
430         self = beam;
431         self.think();
432         self = oldself;
433 }
434
435 float W_Arc(float req)
436 {
437         switch(req)
438         {
439                 case WR_AIM:
440                 {
441                         if(WEP_CVAR(arc, beam_botaimspeed))
442                                 self.BUTTON_ATCK = bot_aim(WEP_CVAR(arc, beam_botaimspeed), 0, WEP_CVAR(arc, beam_botaimlifetime), FALSE);
443                         else
444                                 self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE);
445                         return TRUE;
446                 }
447                 case WR_THINK:
448                 {
449                         if(self.BUTTON_ATCK)
450                         {
451                                 if(self.BUTTON_ATCK_prev) // TODO: Find another way to implement this!
452                                         /*if(self.animstate_startframe == self.anim_shoot_x && self.animstate_numframes == self.anim_shoot_y)
453                                                 weapon_thinkf(WFRAME_DONTCHANGE, autocvar_g_balance_arc_primary_animtime, w_ready);
454                                         else*/
455                                                 weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
456                                 
457                                 if(weapon_prepareattack(0, 0))
458                                 {
459                                         if((!self.arc_beam) || wasfreed(self.arc_beam))
460                                                 W_Arc_Beam();
461                                         
462                                         if(!self.BUTTON_ATCK_prev)
463                                         {
464                                                 weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
465                                                 self.BUTTON_ATCK_prev = 1;
466                                         }
467                                 }
468                         } 
469                         else // todo
470                         {
471                                 if(self.BUTTON_ATCK_prev != 0)
472                                 {
473                                         weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
474                                         ATTACK_FINISHED(self) = time + WEP_CVAR(arc, beam_refire) * W_WeaponRateFactor();
475                                 }
476                                 self.BUTTON_ATCK_prev = 0;
477                         }
478
479                         //if(self.BUTTON_ATCK2)
480                                 //if(weapon_prepareattack(1, autocvar_g_balance_arc_secondary_refire))
481                                 //{
482                                 //      W_Arc_Attack2();
483                                 //      self.arc_count = autocvar_g_balance_arc_secondary_count;
484                                 //      weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_arc_secondary_animtime, w_arc_checkattack);
485                                 //      self.arc_secondarytime = time + autocvar_g_balance_arc_secondary_refire2 * W_WeaponRateFactor();
486                                 //}
487                                 
488                         return TRUE;
489                 }
490                 case WR_INIT:
491                 {
492                         precache_model("models/weapons/g_arc.md3");
493                         precache_model("models/weapons/v_arc.md3");
494                         precache_model("models/weapons/h_arc.iqm");
495                         //precache_sound("weapons/arc_bounce.wav");
496                         precache_sound("weapons/arc_fire.wav");
497                         precache_sound("weapons/arc_fire2.wav");
498                         precache_sound("weapons/arc_impact.wav");
499                         //precache_sound("weapons/arc_impact_combo.wav");
500                         //precache_sound("weapons/W_Arc_Beam_fire.wav");
501                         return TRUE;
502                 }
503                 case WR_CHECKAMMO1:
504                 {
505                         return !WEP_CVAR(arc, beam_ammo) || (self.WEP_AMMO(ARC) > 0);
506                 }
507                 case WR_CHECKAMMO2:
508                 {
509                         //return self.WEP_AMMO(ARC) >= WEP_CVAR_SEC(arc, ammo);
510                         return TRUE;
511                 }
512                 case WR_CONFIG:
513                 {
514                         ARC_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS)
515                         return TRUE;
516                 }
517                 case WR_KILLMESSAGE:
518                 {
519                         if(w_deathtype & HITTYPE_SECONDARY)
520                         {
521                                 return WEAPON_ELECTRO_MURDER_ORBS;
522                         }
523                         else
524                         {
525                                 if(w_deathtype & HITTYPE_BOUNCE)
526                                         return WEAPON_ELECTRO_MURDER_COMBO;
527                                 else
528                                         return WEAPON_ELECTRO_MURDER_BOLT;
529                         }
530                 }
531                 case WR_RESETPLAYER:
532                 {
533                         //self.arc_secondarytime = time;
534                         return TRUE;
535                 }
536         }
537         return FALSE;
538 }
539
540 void ArcInit(void)
541 {
542         WEP_ACTION(WEP_ARC, WR_INIT);
543         arc_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 1);
544         arc_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 2);
545         arc_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 3);
546         arc_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 4);
547         ARC_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP)
548 }
549 #endif
550 #ifdef CSQC
551 float W_Arc(float req)
552 {
553         switch(req)
554         {
555                 case WR_IMPACTEFFECT:
556                 {
557                         vector org2;
558                         org2 = w_org + w_backoff * 6;
559                         
560                         if(w_deathtype & HITTYPE_SECONDARY)
561                         {
562                                 pointparticles(particleeffectnum("arc_ballexplode"), org2, '0 0 0', 1);
563                                 if(!w_issilent)
564                                         sound(self, CH_SHOTS, "weapons/arc_impact.wav", VOL_BASE, ATTN_NORM);
565                         }
566                         else
567                         {
568                                 pointparticles(particleeffectnum("arc_impact"), org2, '0 0 0', 1);
569                                 if(!w_issilent)
570                                         sound(self, CH_SHOTS, "weapons/arc_impact.wav", VOL_BASE, ATTN_NORM);
571                         }
572                         
573                         return TRUE;
574                 }
575                 case WR_INIT:
576                 {
577                         precache_sound("weapons/arc_impact.wav");
578                         precache_sound("weapons/arc_impact_combo.wav");
579                         return TRUE;
580                 }
581                 case WR_ZOOMRETICLE:
582                 {
583                         // no weapon specific image for this weapon
584                         return FALSE;
585                 }
586         }
587         return FALSE;
588 }
589 #endif
590 #endif