]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/weapons/w_arc.qc
Add functionality to "lock in" attack once starting a burst
[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_healing_amax) \
32         w_cvar(id, sn, NONE, beam_healing_aps) \
33         w_cvar(id, sn, NONE, beam_healing_hmax) \
34         w_cvar(id, sn, NONE, beam_healing_hps) \
35         w_cvar(id, sn, NONE, beam_maxangle) \
36         w_cvar(id, sn, NONE, beam_nonplayerdamage) \
37         w_cvar(id, sn, NONE, beam_range) \
38         w_cvar(id, sn, NONE, beam_refire) \
39         w_cvar(id, sn, NONE, beam_returnspeed) \
40         w_cvar(id, sn, NONE, beam_tightness) \
41         w_cvar(id, sn, NONE, burst_damage) \
42         w_cvar(id, sn, NONE, burst_healing_aps) \
43         w_cvar(id, sn, NONE, burst_healing_hps) \
44         w_prop(id, sn, float,  switchdelay_raise, switchdelay_raise) \
45         w_prop(id, sn, float,  switchdelay_drop, switchdelay_drop) \
46         w_prop(id, sn, string, weaponreplace, weaponreplace) \
47         w_prop(id, sn, float,  weaponstart, weaponstart) \
48         w_prop(id, sn, float,  weaponstartoverride, weaponstartoverride) \
49         w_prop(id, sn, float,  weaponthrowable, weaponthrowable)
50
51 #ifndef MENUQC
52 vector arc_shotorigin[4];
53 .vector beam_start;
54 .vector beam_dir;
55 .vector beam_wantdir;
56 .float beam_type;
57 #define ARC_BT_MISS        0
58 #define ARC_BT_WALL        1
59 #define ARC_BT_HEAL        2
60 #define ARC_BT_HIT         3
61 #define ARC_BT_BURST_MISS  10
62 #define ARC_BT_BURST_WALL  11
63 #define ARC_BT_BURST_HEAL  12
64 #define ARC_BT_BURST_HIT   13
65 #define ARC_BT_BURSTMASK   10
66 #endif
67 #ifdef SVQC
68 #define ARC_MAX_SEGMENTS 20
69 ARC_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
70 .entity arc_beam; // used for beam
71 .float BUTTON_ATCK_prev; // for better animation control
72 .float lg_fire_prev; // for better animation control
73 .float beam_initialized;
74 .float beam_bursting;
75 #endif
76 #else
77 #ifdef SVQC
78 void spawnfunc_weapon_arc(void) { weapon_defaultspawnfunc(WEP_ARC); }
79
80 float W_Arc_Beam_Send(entity to, float sf)
81 {
82         WriteByte(MSG_ENTITY, ENT_CLIENT_ARC_BEAM);
83
84         // Truncate information when this beam is displayed to the owner client
85         // - The owner client has no use for beam start position or directions,
86         //    it always figures this information out for itself with csqc code.
87         // - Spectating the owner also truncates this information.
88         float drawlocal = ((to == self.owner) || ((to.enemy == self.owner) && IS_SPEC(to)));
89         if(drawlocal)
90         {
91                 #if 0
92                 sf &= ~2;
93                 sf &= ~4;
94                 sf &= ~8;
95                 #else
96                 sf &= ~14;
97                 #endif
98         }
99
100         WriteByte(MSG_ENTITY, sf);
101
102         if(sf & 1) // settings information
103         {
104                 WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_maxangle));
105                 WriteCoord(MSG_ENTITY, WEP_CVAR(arc, beam_range));
106                 WriteShort(MSG_ENTITY, WEP_CVAR(arc, beam_returnspeed));
107                 WriteByte(MSG_ENTITY, WEP_CVAR(arc, beam_tightness) * 10);
108
109                 WriteByte(MSG_ENTITY, drawlocal);
110         }
111         if(sf & 2) // starting location
112         {
113                 WriteCoord(MSG_ENTITY, self.beam_start_x);
114                 WriteCoord(MSG_ENTITY, self.beam_start_y);
115                 WriteCoord(MSG_ENTITY, self.beam_start_z);
116         }
117         if(sf & 4) // want/aim direction
118         {
119                 WriteCoord(MSG_ENTITY, self.beam_wantdir_x);
120                 WriteCoord(MSG_ENTITY, self.beam_wantdir_y);
121                 WriteCoord(MSG_ENTITY, self.beam_wantdir_z);
122         }
123         if(sf & 8) // beam direction
124         {
125                 WriteCoord(MSG_ENTITY, self.beam_dir_x);
126                 WriteCoord(MSG_ENTITY, self.beam_dir_y);
127                 WriteCoord(MSG_ENTITY, self.beam_dir_z);
128         }
129         if(sf & 16) // beam type
130         {
131                 WriteByte(MSG_ENTITY, self.beam_type);
132         }
133
134         return TRUE;
135 }
136
137 void W_Arc_Beam_Think(void)
138 {
139         float i, burst = 0;
140         if(self != self.owner.arc_beam)
141         {
142                 remove(self);
143                 return;
144         }
145         if((self.owner.WEP_AMMO(ARC) <= 0 && !(self.owner.items & IT_UNLIMITED_WEAPON_AMMO)) || self.owner.deadflag != DEAD_NO || (!self.owner.BUTTON_ATCK && !self.beam_bursting) || self.owner.freezetag_frozen)
146         {
147                 if(self == self.owner.arc_beam) { self.owner.arc_beam = world; } // is this needed? I thought this is changed to world when removed ANYWAY
148                 remove(self);
149                 return;
150         }
151
152         if(self.owner.BUTTON_ATCK2 || self.beam_bursting)
153         {
154                 if(!self.beam_bursting)
155                         self.beam_bursting = TRUE;
156                 burst = ARC_BT_BURSTMASK;
157         }
158
159         // decrease ammo // todo: support burst ammo
160         float dt = frametime;
161         if(!(self.owner.items & IT_UNLIMITED_WEAPON_AMMO))
162         {
163                 if(WEP_CVAR(arc, beam_ammo))
164                 {
165                         dt = min(dt, self.owner.WEP_AMMO(ARC) / WEP_CVAR(arc, beam_ammo));
166                         self.owner.WEP_AMMO(ARC) = max(0, self.owner.WEP_AMMO(ARC) - WEP_CVAR(arc, beam_ammo) * frametime);
167                 }
168         }
169
170         makevectors(self.owner.v_angle);
171
172         W_SetupShot_Range(self.owner, TRUE, 0, "", 0, WEP_CVAR(arc, beam_damage) * dt, WEP_CVAR(arc, beam_range));
173
174         // network information: shot origin and want/aim direction
175         if(self.beam_start != w_shotorg)
176         {
177                 self.SendFlags |= 2;
178                 self.beam_start = w_shotorg;
179         }
180         if(self.beam_wantdir != w_shotdir)
181         {
182                 self.SendFlags |= 4;
183                 self.beam_wantdir = w_shotdir;
184         }
185
186         if(!self.beam_initialized)
187         {
188                 #ifdef ARC_DEBUG
189                 for(i = 0; i < ARC_MAX_SEGMENTS; ++i)
190                         self.lg_ents[i] = spawn();
191                 #endif
192                 
193                 self.beam_dir = w_shotdir;
194                 self.beam_initialized = TRUE;
195         }
196
197         float segments; 
198         if(self.beam_dir != w_shotdir)
199         {
200                 float angle = ceil(vlen(w_shotdir - self.beam_dir) * RAD2DEG);
201                 float anglelimit;
202                 if(angle && (angle > WEP_CVAR(arc, beam_maxangle)))
203                 {
204                         // if the angle is greater than maxangle, force the blendfactor to make this the maximum factor
205                         anglelimit = min(WEP_CVAR(arc, beam_maxangle) / angle, 1);
206                 }
207                 else
208                 {
209                         // the radius is not too far yet, no worries :D
210                         anglelimit = 1;
211                 }
212
213                 // calculate how much we're going to move the end of the beam to the want position
214                 float blendfactor = bound(0, anglelimit * (1 - (WEP_CVAR(arc, beam_returnspeed) * dt)), 1);
215                 self.beam_dir = normalize((w_shotdir * (1 - blendfactor)) + (self.beam_dir * blendfactor));
216
217                 // WEAPONTODO (server and client):
218                 // blendfactor never actually becomes 0 in this situation, which is a problem
219                 // regarding precision... this means that self.beam_dir and w_shotdir approach
220                 // eachother, however they never actually become the same value with this method.
221                 
222                 // Perhaps we should do some form of rounding/snapping?
223
224                 // printf("blendfactor = %f\n", blendfactor);
225
226                 // network information: beam direction
227                 self.SendFlags |= 8;
228
229                 // calculate how many segments are needed
230                 float max_allowed_segments;
231
232                 if(WEP_CVAR(arc, beam_distancepersegment))
233                         max_allowed_segments = min(ARC_MAX_SEGMENTS, 1 + (vlen(w_shotdir / WEP_CVAR(arc, beam_distancepersegment))));
234                 else
235                         max_allowed_segments = ARC_MAX_SEGMENTS;
236
237                 if(WEP_CVAR(arc, beam_degreespersegment))
238                 {
239                         segments = min( max(1, ( min(angle, WEP_CVAR(arc, beam_maxangle)) / WEP_CVAR(arc, beam_degreespersegment) ) ), max_allowed_segments );
240                 }
241                 else
242                 {
243                         segments = 1;
244                 }
245         }
246         else
247         {
248                 segments = 1;
249         }
250
251         vector beam_endpos_estimate = (w_shotorg + (self.beam_dir * WEP_CVAR(arc, beam_range)));
252
253         float new_beam_type = 0;
254         vector last_origin = w_shotorg;
255         for(i = 1; i <= segments; ++i)
256         {
257                 // WEAPONTODO (server and client):
258                 // Segment blend and distance should probably really be calculated in a better way,
259                 // however I am not sure how to do it properly. There are a few things I have tried,
260                 // but most of them do not work properly due to my lack of understanding regarding
261                 // the mathematics behind them.
262
263                 // Ideally, we should calculate the positions along a perfect curve
264                 // between wantdir and self.beam_dir with an option for depth of arc
265
266                 // Another issue is that (on the client code) we must separate the
267                 // curve into multiple rendered curves when handling warpzones.
268                 
269                 // I can handle this by detecting it for each segment, however that
270                 // is a fairly inefficient method in comparison to having a curved line
271                 // drawing function similar to Draw_CylindricLine that accepts
272                 // top and bottom origins as input, this way there would be no
273                 // overlapping edges when connecting the curved pieces.
274
275                 // WEAPONTODO (client):
276                 // In order to do nice fading and pointing on the starting segment, we must always
277                 // have that drawn as a separate triangle... However, that is difficult to do when
278                 // keeping in mind the above problems and also optimizing the amount of segments
279                 // drawn on screen at any given time. (Automatic beam quality scaling, essentially)
280
281                 // calculate this on every segment to ensure that we always reach the full length of the attack
282                 float segmentblend = bound(0, (i/segments) + WEP_CVAR(arc, beam_tightness), 1);
283                 float segmentdist = vlen(beam_endpos_estimate - last_origin) * (i/segments);
284
285                 vector new_dir = normalize( (w_shotdir * (1 - segmentblend)) + (normalize(beam_endpos_estimate - last_origin) * segmentblend) );
286                 vector new_origin = last_origin + (new_dir * segmentdist);
287
288                 WarpZone_traceline_antilag(
289                         self.owner,
290                         last_origin,
291                         new_origin,
292                         MOVE_NORMAL,
293                         self.owner,
294                         ANTILAG_LATENCY(self.owner)
295                 );
296
297                 float is_player = (trace_ent.classname == "player" || trace_ent.classname == "body" || (trace_ent.flags & FL_MONSTER));
298                 if(trace_ent && trace_ent.takedamage && (is_player || WEP_CVAR(arc, beam_nonplayerdamage)))
299                 {
300                         // calculate our own hit origin as trace_endpos tends to jump around annoyingly (to player origin?)
301                         vector hitorigin = last_origin + (new_dir * segmentdist * trace_fraction);
302
303                         float falloff = ExponentialFalloff(
304                                 WEP_CVAR(arc, beam_falloff_mindist),
305                                 WEP_CVAR(arc, beam_falloff_maxdist),
306                                 WEP_CVAR(arc, beam_falloff_halflifedist),
307                                 vlen(WarpZone_UnTransformOrigin(WarpZone_trace_transform, hitorigin) - w_shotorg)
308                         );
309
310                         if(is_player && SAME_TEAM(self.owner, trace_ent))
311                         {
312                                 float roothealth, rootarmor;
313                                 if(burst)
314                                 {
315                                         roothealth = WEP_CVAR(arc, burst_healing_hps);
316                                         rootarmor = WEP_CVAR(arc, burst_healing_aps);
317                                 }
318                                 else
319                                 {
320                                         roothealth = WEP_CVAR(arc, beam_healing_hps);
321                                         rootarmor = WEP_CVAR(arc, beam_healing_aps);
322                                 }
323
324                                 if(trace_ent.health <= WEP_CVAR(arc, beam_healing_hmax) && roothealth)
325                                 {
326                                         trace_ent.health = min(trace_ent.health + (roothealth * dt), WEP_CVAR(arc, beam_healing_hmax));
327                                 }
328                                 if(trace_ent.armorvalue <= WEP_CVAR(arc, beam_healing_amax) && rootarmor)
329                                 {
330                                         trace_ent.armorvalue = min(trace_ent.armorvalue + (rootarmor * dt), WEP_CVAR(arc, beam_healing_amax));
331                                 }
332
333                                 // stop rot, set visual effect
334                                 if(roothealth || rootarmor)
335                                 {
336                                         trace_ent.pauserothealth_finished = max(trace_ent.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
337                                         trace_ent.pauserotarmor_finished = max(trace_ent.pauserotarmor_finished, time + autocvar_g_balance_pause_armor_rot);
338                                         new_beam_type = ARC_BT_HEAL;
339                                 }
340                         }
341                         else
342                         {
343                                 float rootdamage;
344                                 if(is_player)
345                                 {
346                                         if(burst)
347                                                 { rootdamage = WEP_CVAR(arc, burst_damage); }
348                                         else
349                                                 { rootdamage = WEP_CVAR(arc, beam_damage); }
350                                 }
351                                 else
352                                         { rootdamage = WEP_CVAR(arc, beam_nonplayerdamage); }
353
354                                 if(accuracy_isgooddamage(self.owner, trace_ent))
355                                 {
356                                         accuracy_add(
357                                                 self.owner,
358                                                 WEP_ARC,
359                                                 0,
360                                                 rootdamage * dt * falloff
361                                         );
362                                 }
363
364                                 Damage(
365                                         trace_ent,
366                                         self.owner,
367                                         self.owner,
368                                         rootdamage * dt * falloff,
369                                         WEP_ARC,
370                                         hitorigin,
371                                         WEP_CVAR(arc, beam_force) * new_dir * dt * falloff
372                                 );
373
374                                 new_beam_type = ARC_BT_HIT;
375                         }
376                         break; 
377                 }
378                 else if(trace_fraction != 1)
379                 {
380                         // we collided with geometry
381                         new_beam_type = ARC_BT_WALL;
382                         break;
383                 }
384                 else
385                 {
386                         last_origin = new_origin;
387                 }
388         }
389
390         // if we're bursting, use burst visual effects
391         new_beam_type += burst;
392
393         // network information: beam type
394         if(new_beam_type != self.beam_type)
395         {
396                 self.SendFlags |= 16;
397                 self.beam_type = new_beam_type;
398         }
399
400         self.owner.lg_fire_prev = time;
401         self.nextthink = time;
402 }
403
404 // Attack functions ========================= 
405 void W_Arc_Beam(float bursting)
406 {
407         // only play fire sound if 1 sec has passed since player let go the fire button
408         if(time - self.lg_fire_prev > 1)
409         {
410                 sound(self, CH_WEAPON_A, "weapons/lgbeam_fire.wav", VOL_BASE, ATTN_NORM);
411         }
412
413         entity beam, oldself;
414
415         self.arc_beam = beam = spawn();
416         beam.classname = "W_Arc_Beam";
417         beam.solid = SOLID_NOT;
418         beam.think = W_Arc_Beam_Think;
419         beam.owner = self;
420         beam.movetype = MOVETYPE_NONE;
421         //beam.shot_spread = 1;
422         beam.bot_dodge = TRUE;
423         beam.bot_dodgerating = WEP_CVAR(arc, beam_damage);
424         beam.beam_bursting = bursting;
425         Net_LinkEntity(beam, FALSE, 0, W_Arc_Beam_Send);
426
427         oldself = self;
428         self = beam;
429         self.think();
430         self = oldself;
431 }
432
433 float W_Arc(float req)
434 {
435         switch(req)
436         {
437                 case WR_AIM:
438                 {
439                         if(WEP_CVAR(arc, beam_botaimspeed))
440                                 self.BUTTON_ATCK = bot_aim(WEP_CVAR(arc, beam_botaimspeed), 0, WEP_CVAR(arc, beam_botaimlifetime), FALSE);
441                         else
442                                 self.BUTTON_ATCK = bot_aim(1000000, 0, 0.001, FALSE);
443                         return TRUE;
444                 }
445                 case WR_THINK:
446                 {
447                         if(self.BUTTON_ATCK || self.arc_beam.beam_bursting)
448                         {
449                                 if(self.BUTTON_ATCK_prev) // TODO: Find another way to implement this!
450                                         /*if(self.animstate_startframe == self.anim_shoot_x && self.animstate_numframes == self.anim_shoot_y)
451                                                 weapon_thinkf(WFRAME_DONTCHANGE, autocvar_g_balance_arc_primary_animtime, w_ready);
452                                         else*/
453                                                 weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
454                                 
455                                 if(weapon_prepareattack(0, 0))
456                                 {
457                                         if((!self.arc_beam) || wasfreed(self.arc_beam))
458                                                 W_Arc_Beam(FALSE);
459                                         
460                                         if(!self.BUTTON_ATCK_prev)
461                                         {
462                                                 weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
463                                                 self.BUTTON_ATCK_prev = 1;
464                                         }
465                                 }
466                         } 
467                         else // todo
468                         {
469                                 if(self.BUTTON_ATCK_prev != 0)
470                                 {
471                                         weapon_thinkf(WFRAME_FIRE1, WEP_CVAR(arc, beam_animtime), w_ready);
472                                         ATTACK_FINISHED(self) = time + WEP_CVAR(arc, beam_refire) * W_WeaponRateFactor();
473                                 }
474                                 self.BUTTON_ATCK_prev = 0;
475                         }
476
477                         #if 0
478                         if(self.BUTTON_ATCK2)
479                         if(weapon_prepareattack(1, autocvar_g_balance_arc_secondary_refire))
480                         {
481                                 W_Arc_Attack2();
482                                 self.arc_count = autocvar_g_balance_arc_secondary_count;
483                                 weapon_thinkf(WFRAME_FIRE2, autocvar_g_balance_arc_secondary_animtime, w_arc_checkattack);
484                                 self.arc_secondarytime = time + autocvar_g_balance_arc_secondary_refire2 * W_WeaponRateFactor();
485                         }
486                         #endif
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_fire.wav");
496                         precache_sound("weapons/arc_fire2.wav");
497                         precache_sound("weapons/arc_impact.wav");
498                         if(!arc_shotorigin[0])
499                         {
500                                 arc_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 1);
501                                 arc_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 2);
502                                 arc_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 3);
503                                 arc_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_ARC), FALSE, FALSE, 4);
504                         }
505                         ARC_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP)
506                         return TRUE;
507                 }
508                 case WR_CHECKAMMO1:
509                 {
510                         return !WEP_CVAR(arc, beam_ammo) || (self.WEP_AMMO(ARC) > 0);
511                 }
512                 case WR_CHECKAMMO2:
513                 {
514                         //return self.WEP_AMMO(ARC) >= WEP_CVAR_SEC(arc, ammo);
515                         return TRUE;
516                 }
517                 case WR_CONFIG:
518                 {
519                         ARC_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS)
520                         return TRUE;
521                 }
522                 case WR_KILLMESSAGE:
523                 {
524                         if(w_deathtype & HITTYPE_SECONDARY)
525                         {
526                                 return WEAPON_ELECTRO_MURDER_ORBS;
527                         }
528                         else
529                         {
530                                 if(w_deathtype & HITTYPE_BOUNCE)
531                                         return WEAPON_ELECTRO_MURDER_COMBO;
532                                 else
533                                         return WEAPON_ELECTRO_MURDER_BOLT;
534                         }
535                 }
536                 case WR_RESETPLAYER:
537                 {
538                         //self.arc_secondarytime = time;
539                         return TRUE;
540                 }
541         }
542         return FALSE;
543 }
544 #endif
545 #ifdef CSQC
546 float W_Arc(float req)
547 {
548         switch(req)
549         {
550                 case WR_IMPACTEFFECT:
551                 {
552                         // todo
553                         return TRUE;
554                 }
555                 case WR_INIT:
556                 {
557                         precache_sound("weapons/arc_impact.wav");
558                         precache_sound("weapons/arc_impact_combo.wav");
559                         return TRUE;
560                 }
561                 case WR_ZOOMRETICLE:
562                 {
563                         // no weapon specific image for this weapon
564                         return FALSE;
565                 }
566         }
567         return FALSE;
568 }
569 #endif
570 #endif