]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/weapons/weapon/hook.qc
Merge branch 'martin-t/angles' into 'master'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / weapons / weapon / hook.qc
1 #include "hook.qh"
2
3 #ifdef SVQC
4
5 spawnfunc(weapon_hook) { weapon_defaultspawnfunc(this, WEP_HOOK); }
6
7 void W_Hook_ExplodeThink(entity this)
8 {
9         float dt, dmg_remaining_next, f;
10
11         dt = time - this.teleport_time;
12         dmg_remaining_next = (bound(0, 1 - dt / this.dmg_duration, 1) ** this.dmg_power);
13
14         f = this.dmg_last - dmg_remaining_next;
15         this.dmg_last = dmg_remaining_next;
16
17         RadiusDamage(this, this.realowner, this.dmg * f, this.dmg_edge * f, this.dmg_radius, this.realowner, NULL, this.dmg_force * f, this.projectiledeathtype, NULL);
18         this.projectiledeathtype |= HITTYPE_BOUNCE;
19         //RadiusDamage(this, NULL, this.dmg * f, this.dmg_edge * f, this.dmg_radius, NULL, NULL, this.dmg_force * f, this.projectiledeathtype, NULL);
20
21         if(dt < this.dmg_duration)
22                 this.nextthink = time + 0.05; // soon
23         else
24                 delete(this);
25 }
26
27 void W_Hook_Explode2(entity this)
28 {
29         this.event_damage = func_null;
30         settouch(this, func_null);
31         this.effects |= EF_NODRAW;
32
33         setthink(this, W_Hook_ExplodeThink);
34         this.nextthink = time;
35         this.dmg = WEP_CVAR_SEC(hook, damage);
36         this.dmg_edge = WEP_CVAR_SEC(hook, edgedamage);
37         this.dmg_radius = WEP_CVAR_SEC(hook, radius);
38         this.dmg_force = WEP_CVAR_SEC(hook, force);
39         this.dmg_power = WEP_CVAR_SEC(hook, power);
40         this.dmg_duration = WEP_CVAR_SEC(hook, duration);
41         this.teleport_time = time;
42         this.dmg_last = 1;
43         set_movetype(this, MOVETYPE_NONE);
44 }
45
46 void W_Hook_Explode2_use(entity this, entity actor, entity trigger)
47 {
48         W_Hook_Explode2(this);
49 }
50
51 void W_Hook_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
52 {
53         if(this.health <= 0)
54                 return;
55
56         if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions
57                 return; // g_projectiles_damage says to halt
58
59         this.health = this.health - damage;
60
61         if(this.health <= 0)
62                 W_PrepareExplosionByDamage(this, this.realowner, W_Hook_Explode2);
63 }
64
65 void W_Hook_Touch2(entity this, entity toucher)
66 {
67         PROJECTILE_TOUCH(this, toucher);
68         this.use(this, NULL, NULL);
69 }
70
71 void W_Hook_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
72 {
73         //W_DecreaseAmmo(thiswep, actor, WEP_CVAR_SEC(hook, ammo)); // WEAPONTODO: Figure out how to handle ammo with hook secondary (gravitybomb)
74         W_SetupShot(actor, weaponentity, false, 4, SND_HOOKBOMB_FIRE, CH_WEAPON_A, WEP_CVAR_SEC(hook, damage));
75
76         entity gren = new(hookbomb);
77         gren.owner = gren.realowner = actor;
78         gren.bot_dodge = true;
79         gren.bot_dodgerating = WEP_CVAR_SEC(hook, damage);
80         set_movetype(gren, MOVETYPE_TOSS);
81         PROJECTILE_MAKETRIGGER(gren);
82         gren.projectiledeathtype = WEP_HOOK.m_id | HITTYPE_SECONDARY;
83         setorigin(gren, w_shotorg);
84         setsize(gren, '0 0 0', '0 0 0');
85
86         gren.nextthink = time + WEP_CVAR_SEC(hook, lifetime);
87         setthink(gren, adaptor_think2use_hittype_splash);
88         gren.use = W_Hook_Explode2_use;
89         settouch(gren, W_Hook_Touch2);
90
91         gren.takedamage = DAMAGE_YES;
92         gren.health = WEP_CVAR_SEC(hook, health);
93         gren.damageforcescale = WEP_CVAR_SEC(hook, damageforcescale);
94         gren.event_damage = W_Hook_Damage;
95         gren.damagedbycontents = true;
96         IL_PUSH(g_damagedbycontents, gren);
97         gren.missile_flags = MIF_SPLASH | MIF_ARC;
98
99         gren.velocity = '0 0 1' * WEP_CVAR_SEC(hook, speed);
100         if (autocvar_g_projectiles_newton_style)
101                 gren.velocity = gren.velocity + actor.velocity;
102
103         gren.gravity = WEP_CVAR_SEC(hook, gravity);
104         //W_SetupProjVelocity_Basic(gren); // just falling down!
105
106         gren.angles = '0 0 0';
107         gren.flags = FL_PROJECTILE;
108         IL_PUSH(g_projectiles, gren);
109         IL_PUSH(g_bot_dodge, gren);
110
111         CSQCProjectile(gren, true, PROJECTILE_HOOKBOMB, true);
112
113         MUTATOR_CALLHOOK(EditProjectile, actor, gren);
114 }
115
116 METHOD(Hook, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
117 {
118     if (fire & 1) {
119         if(!actor.(weaponentity).hook)
120         if(!(actor.(weaponentity).hook_state & HOOK_WAITING_FOR_RELEASE))
121         if(time > actor.(weaponentity).hook_refire)
122         if(weapon_prepareattack(thiswep, actor, weaponentity, false, -1))
123         {
124             W_DecreaseAmmo(thiswep, actor, thiswep.ammo_factor * WEP_CVAR_PRI(hook, ammo), weaponentity);
125             actor.(weaponentity).hook_state |= HOOK_FIRING;
126             actor.(weaponentity).hook_state |= HOOK_WAITING_FOR_RELEASE;
127             weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(hook, animtime), w_ready);
128         }
129     } else {
130         actor.(weaponentity).hook_state |= HOOK_REMOVING;
131         actor.(weaponentity).hook_state &= ~HOOK_WAITING_FOR_RELEASE;
132     }
133
134     if(fire & 2)
135     {
136         if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(hook, refire)))
137         {
138             W_Hook_Attack2(thiswep, actor, weaponentity);
139             weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(hook, animtime), w_ready);
140         }
141     }
142
143     if(actor.(weaponentity).hook)
144     {
145         // if hooked, no bombs, and increase the timer
146         actor.(weaponentity).hook_refire = max(actor.(weaponentity).hook_refire, time + WEP_CVAR_PRI(hook, refire) * W_WeaponRateFactor(actor));
147
148         // hook also inhibits health regeneration, but only for 1 second
149         if(!(actor.items & IT_UNLIMITED_WEAPON_AMMO))
150             actor.pauseregen_finished = max(actor.pauseregen_finished, time + autocvar_g_balance_pause_fuel_regen);
151     }
152
153     if(actor.(weaponentity).hook && actor.(weaponentity).hook.state == 1)
154     {
155         float hooked_time_max = WEP_CVAR_PRI(hook, hooked_time_max);
156         if(hooked_time_max > 0)
157         {
158             if( time > actor.(weaponentity).hook_time_hooked + hooked_time_max )
159                 actor.(weaponentity).hook_state |= HOOK_REMOVING;
160         }
161
162         float hooked_fuel = thiswep.ammo_factor * WEP_CVAR_PRI(hook, hooked_ammo);
163         if(hooked_fuel > 0)
164         {
165             if( time > actor.(weaponentity).hook_time_fueldecrease )
166             {
167                 if(!(actor.items & IT_UNLIMITED_WEAPON_AMMO))
168                 {
169                     if( actor.ammo_fuel >= (time - actor.(weaponentity).hook_time_fueldecrease) * hooked_fuel )
170                     {
171                         W_DecreaseAmmo(thiswep, actor, (time - actor.(weaponentity).hook_time_fueldecrease) * hooked_fuel, weaponentity);
172                         actor.(weaponentity).hook_time_fueldecrease = time;
173                         // decrease next frame again
174                     }
175                     else
176                     {
177                         actor.ammo_fuel = 0;
178                         actor.(weaponentity).hook_state |= HOOK_REMOVING;
179                         if(actor.(weaponentity).m_weapon != WEP_Null) // offhand
180                                 W_SwitchWeapon_Force(actor, w_getbestweapon(actor, weaponentity), weaponentity);
181                     }
182                 }
183             }
184         }
185     }
186     else
187     {
188         actor.(weaponentity).hook_time_hooked = time;
189         actor.(weaponentity).hook_time_fueldecrease = time + WEP_CVAR_PRI(hook, hooked_time_free);
190     }
191
192     actor.(weaponentity).hook_state = BITSET(actor.(weaponentity).hook_state, HOOK_PULLING, (!PHYS_INPUT_BUTTON_CROUCH(actor) || !autocvar_g_balance_grapplehook_crouchslide));
193
194     if (actor.(weaponentity).hook_state & HOOK_FIRING)
195     {
196         if (actor.(weaponentity).hook)
197             RemoveHook(actor.(weaponentity).hook);
198         FireGrapplingHook(actor, weaponentity);
199         actor.(weaponentity).hook_state &= ~HOOK_FIRING;
200         actor.(weaponentity).hook_refire = max(actor.(weaponentity).hook_refire, time + autocvar_g_balance_grapplehook_refire * W_WeaponRateFactor(actor));
201     }
202     else if (actor.(weaponentity).hook_state & HOOK_REMOVING)
203     {
204         if (actor.(weaponentity).hook)
205             RemoveHook(actor.(weaponentity).hook);
206         actor.(weaponentity).hook_state &= ~HOOK_REMOVING;
207     }
208 }
209 METHOD(Hook, wr_setup, void(entity thiswep, entity actor, .entity weaponentity))
210 {
211         actor.(weaponentity).hook_state &= ~HOOK_WAITING_FOR_RELEASE;
212 }
213 METHOD(Hook, wr_checkammo1, bool(Hook thiswep, entity actor, .entity weaponentity))
214 {
215     if (!thiswep.ammo_factor) return true;
216
217     if(actor.(weaponentity).hook)
218         return actor.ammo_fuel > 0;
219
220     return actor.ammo_fuel >= WEP_CVAR_PRI(hook, ammo);
221 }
222 METHOD(Hook, wr_checkammo2, bool(Hook thiswep, entity actor, .entity weaponentity))
223 {
224     // infinite ammo for now
225     return true; // actor.ammo_cells >= WEP_CVAR_SEC(hook, ammo); // WEAPONTODO: see above
226 }
227 METHOD(Hook, wr_resetplayer, void(entity thiswep, entity actor))
228 {
229     RemoveGrapplingHooks(actor);
230     for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
231     {
232         .entity weaponentity = weaponentities[slot];
233         actor.(weaponentity).hook_time = 0;
234         actor.(weaponentity).hook_refire = time;
235     }
236 }
237 METHOD(Hook, wr_killmessage, Notification(entity thiswep))
238 {
239     return WEAPON_HOOK_MURDER;
240 }
241
242 #endif
243 #ifdef CSQC
244
245 METHOD(Hook, wr_impacteffect, void(entity thiswep, entity actor))
246 {
247     vector org2;
248     org2 = w_org + w_backoff * 2;
249     pointparticles(EFFECT_HOOK_EXPLODE, org2, '0 0 0', 1);
250     if(!w_issilent)
251         sound(actor, CH_SHOTS, SND_HOOKBOMB_IMPACT, VOL_BASE, ATTN_NORM);
252 }
253
254 #endif
255
256 #ifdef CSQC
257 #include <lib/csqcmodel/interpolate.qh>
258 #include <lib/warpzone/common.qh>
259
260 float autocvar_cl_grapplehook_alpha = 1;
261
262 void Draw_CylindricLine(vector from, vector to, float thickness, string texture, float aspect, float shift, vector rgb, float theAlpha, float drawflag, vector vieworg);
263
264 entityclass(Hook);
265 class(Hook) .entity HookType; // ENT_CLIENT_*
266 class(Hook) .vector origin;
267 class(Hook) .vector velocity;
268 class(Hook) .float HookSilent;
269 class(Hook) .float HookRange;
270
271 string Draw_GrapplingHook_trace_callback_tex;
272 float Draw_GrapplingHook_trace_callback_rnd;
273 vector Draw_GrapplingHook_trace_callback_rgb;
274 float Draw_GrapplingHook_trace_callback_a;
275 void Draw_GrapplingHook_trace_callback(vector start, vector hit, vector end)
276 {
277         float i;
278         vector vorg;
279         vorg = WarpZone_TransformOrigin(WarpZone_trace_transform, view_origin);
280         for(i = 0; i < Draw_GrapplingHook_trace_callback_a; ++i)
281                 Draw_CylindricLine(hit, start, 8, Draw_GrapplingHook_trace_callback_tex, 0.25, Draw_GrapplingHook_trace_callback_rnd, Draw_GrapplingHook_trace_callback_rgb, min(1, Draw_GrapplingHook_trace_callback_a - i), DRAWFLAG_NORMAL, vorg);
282         Draw_GrapplingHook_trace_callback_rnd += 0.25 * vlen(hit - start) / 8;
283 }
284
285 class(Hook) .float teleport_time;
286 void Draw_GrapplingHook(entity this)
287 {
288         vector a, b, atrans;
289         string tex;
290         vector rgb;
291         float t;
292         vector vs;
293         float intensity, offset;
294
295         if(this.teleport_time)
296         if(time > this.teleport_time)
297         {
298                 sound (this, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, ATTEN_NORM); // safeguard
299                 this.teleport_time = 0;
300         }
301
302         InterpolateOrigin_Do(this);
303
304         int s = W_GunAlign(viewmodels[this.cnt], STAT(GUNALIGN)) - 1;
305
306         switch(this.HookType)
307         {
308                 default:
309                 case NET_ENT_CLIENT_HOOK:
310                         vs = hook_shotorigin[s];
311                         break;
312                 case NET_ENT_CLIENT_ARC_BEAM:
313                         vs = lightning_shotorigin[s];
314                         break;
315         }
316
317         if((this.owner.sv_entnum == player_localentnum - 1))
318         {
319                 switch(this.HookType)
320                 {
321                         default:
322                         case NET_ENT_CLIENT_HOOK:
323                                 if(autocvar_chase_active > 0)
324                                         a = csqcplayer.origin;
325                                 else
326                                         a = view_origin + view_forward * vs.x + view_right * -vs.y + view_up * vs.z;
327                                 b = this.origin;
328                                 break;
329                         case NET_ENT_CLIENT_ARC_BEAM:
330                                 if(this.HookRange)
331                                         b = view_origin + view_forward * this.HookRange;
332                                 else
333                                         b = view_origin + view_forward * vlen(this.velocity - this.origin); // honor original length of beam!
334                                 WarpZone_TraceLine(view_origin, b, MOVE_NORMAL, NULL);
335                                 b = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos);
336                                 a = view_origin + view_forward * vs.x + view_right * -vs.y + view_up * vs.z;
337                                 break;
338                 }
339         }
340         else
341         {
342                 switch(this.HookType)
343                 {
344                         default:
345                         case NET_ENT_CLIENT_HOOK:
346                                 a = this.velocity;
347                                 b = this.origin;
348                                 break;
349                         case NET_ENT_CLIENT_ARC_BEAM:
350                                 a = this.origin;
351                                 b = this.velocity;
352                                 break;
353                 }
354         }
355
356         t = entcs_GetTeamColor(this.owner.sv_entnum);
357
358         switch(this.HookType)
359         {
360                 default:
361                 case NET_ENT_CLIENT_HOOK:
362                         intensity = autocvar_cl_grapplehook_alpha;
363                         offset = 0;
364                         switch(t)
365                         {
366                                 case NUM_TEAM_1: tex = "particles/hook_red"; rgb = '1 0.3 0.3'; break;
367                                 case NUM_TEAM_2: tex = "particles/hook_blue"; rgb = '0.3 0.3 1'; break;
368                                 case NUM_TEAM_3: tex = "particles/hook_yellow"; rgb = '1 1 0.3'; break;
369                                 case NUM_TEAM_4: tex = "particles/hook_pink"; rgb = '1 0.3 1'; break;
370                                 default: tex = "particles/hook_white"; rgb = entcs_GetColor(this.sv_entnum - 1); break;
371                         }
372                         break;
373                 case NET_ENT_CLIENT_ARC_BEAM: // todo
374                         intensity = bound(0.2, 1 + Noise_Pink(this, frametime) * 1 + Noise_Burst(this, frametime, 0.03) * 0.3, 2);
375                         offset = Noise_Brown(this, frametime) * 10;
376                         tex = "particles/lgbeam";
377                         rgb = '1 1 1';
378                         break;
379         }
380
381         MUTATOR_CALLHOOK(DrawGrapplingHook, this, tex, rgb, t);
382         tex = M_ARGV(1, string);
383         rgb = M_ARGV(2, vector);
384
385         Draw_GrapplingHook_trace_callback_tex = tex;
386         Draw_GrapplingHook_trace_callback_rnd = offset;
387         Draw_GrapplingHook_trace_callback_rgb = rgb;
388         Draw_GrapplingHook_trace_callback_a = intensity;
389         WarpZone_TraceBox_ThroughZone(a, '0 0 0', '0 0 0', b, ((this.HookType == NET_ENT_CLIENT_HOOK) ? MOVE_NOTHING : MOVE_NORMAL), NULL, NULL, Draw_GrapplingHook_trace_callback);
390         Draw_GrapplingHook_trace_callback_tex = string_null;
391
392         atrans = WarpZone_TransformOrigin(WarpZone_trace_transform, a);
393
394         switch(this.HookType)
395         {
396                 default:
397                 case NET_ENT_CLIENT_HOOK:
398                         if(vdist(trace_endpos - atrans, >, 0.5))
399                         {
400                                 setorigin(this, trace_endpos); // hook endpoint!
401                                 this.angles = vectoangles(trace_endpos - atrans);
402                                 this.drawmask = MASK_NORMAL;
403                         }
404                         else
405                         {
406                                 this.drawmask = 0;
407                         }
408                         break;
409                 case NET_ENT_CLIENT_ARC_BEAM:
410                         setorigin(this, a); // beam origin!
411                         break;
412         }
413
414         switch(this.HookType)
415         {
416                 default:
417                 case NET_ENT_CLIENT_HOOK:
418                         break;
419                 case NET_ENT_CLIENT_ARC_BEAM:
420                         pointparticles(EFFECT_ARC_LIGHTNING2, trace_endpos, normalize(atrans - trace_endpos), frametime * intensity); // todo: new effect
421                         break;
422         }
423 }
424
425 void Remove_GrapplingHook(entity this)
426 {
427         sound (this, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, ATTEN_NORM);
428
429         for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
430         {
431                 entity wep = viewmodels[slot];
432                 if(wep.hook == this)
433                         wep.hook = NULL;
434         }
435 }
436
437 NET_HANDLE(ENT_CLIENT_HOOK, bool bIsNew)
438 {
439         this.HookType = NET_ENT_CLIENT_HOOK;
440
441         int sf = ReadByte();
442
443         this.HookSilent = (sf & 0x80);
444         this.iflags = IFLAG_VELOCITY | IFLAG_ORIGIN;
445
446         InterpolateOrigin_Undo(this);
447
448         if(sf & 1)
449         {
450                 int myowner = ReadByte();
451                 int slot = ReadByte();
452                 this.owner = playerslots[myowner - 1];
453                 this.sv_entnum = myowner;
454                 if(myowner == player_localentnum)
455                         viewmodels[slot].hook = this;
456                 this.cnt = slot;
457                 switch(this.HookType)
458                 {
459                         default:
460                         case NET_ENT_CLIENT_HOOK:
461                                 this.HookRange = 0;
462                                 break;
463                         case NET_ENT_CLIENT_ARC_BEAM:
464                                 this.HookRange = ReadCoord();
465                                 break;
466                 }
467         }
468         if(sf & 2)
469         {
470                 this.origin_x = ReadCoord();
471                 this.origin_y = ReadCoord();
472                 this.origin_z = ReadCoord();
473                 setorigin(this, this.origin);
474         }
475         if(sf & 4)
476         {
477                 this.velocity_x = ReadCoord();
478                 this.velocity_y = ReadCoord();
479                 this.velocity_z = ReadCoord();
480         }
481
482         InterpolateOrigin_Note(this);
483
484         if(bIsNew || !this.teleport_time)
485         {
486                 this.draw = Draw_GrapplingHook;
487                 IL_PUSH(g_drawables, this);
488                 this.entremove = Remove_GrapplingHook;
489
490                 switch(this.HookType)
491                 {
492                         default:
493                         case NET_ENT_CLIENT_HOOK:
494                                 // for the model
495                                 setmodel(this, MDL_HOOK);
496                                 this.drawmask = MASK_NORMAL;
497                                 break;
498                         case NET_ENT_CLIENT_ARC_BEAM:
499                                 sound (this, CH_SHOTS_SINGLE, SND_LGBEAM_FLY, VOL_BASE, ATTEN_NORM);
500                                 break;
501                 }
502         }
503
504         this.teleport_time = time + 10;
505         return true;
506 }
507
508 // TODO: hook: temporarily transform this.origin for drawing the model along warpzones!
509 #endif