]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/weapons/weapon/hook.qc
b7f51b54c4319c66590ef942576399b2a898ddd4
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / weapons / weapon / hook.qc
1 #ifndef IMPLEMENTATION
2 REGISTER_WEAPON(
3 /* WEP_##id  */ HOOK,
4 /* function  */ W_Hook,
5 /* ammotype  */ ammo_fuel,
6 /* impulse   */ 0,
7 /* flags     */ WEP_FLAG_CANCLIMB | WEP_TYPE_SPLASH,
8 /* rating    */ 0,
9 /* color     */ '0 0.5 0',
10 /* modelname */ "hookgun",
11 /* simplemdl */ "foobar",
12 /* crosshair */ "gfx/crosshairhook 0.5",
13 /* wepimg    */ "weaponhook",
14 /* refname   */ "hook",
15 /* wepname   */ _("Grappling Hook")
16 );
17
18 #define HOOK_SETTINGS(w_cvar,w_prop) HOOK_SETTINGS_LIST(w_cvar, w_prop, HOOK, hook)
19 #define HOOK_SETTINGS_LIST(w_cvar,w_prop,id,sn) \
20         w_cvar(id, sn, BOTH, animtime) \
21         w_cvar(id, sn, BOTH, refire) \
22         w_cvar(id, sn, PRI,  ammo) \
23         w_cvar(id, sn, PRI,  hooked_ammo) \
24         w_cvar(id, sn, PRI,  hooked_time_free) \
25         w_cvar(id, sn, PRI,  hooked_time_max) \
26         w_cvar(id, sn, SEC,  damage) \
27         w_cvar(id, sn, SEC,  duration) \
28         w_cvar(id, sn, SEC,  edgedamage) \
29         w_cvar(id, sn, SEC,  force) \
30         w_cvar(id, sn, SEC,  gravity) \
31         w_cvar(id, sn, SEC,  lifetime) \
32         w_cvar(id, sn, SEC,  power) \
33         w_cvar(id, sn, SEC,  radius) \
34         w_cvar(id, sn, SEC,  speed) \
35         w_cvar(id, sn, SEC,  health) \
36         w_cvar(id, sn, SEC,  damageforcescale) \
37         w_prop(id, sn, float,  switchdelay_raise, switchdelay_raise) \
38         w_prop(id, sn, float,  switchdelay_drop, switchdelay_drop) \
39         w_prop(id, sn, string, weaponreplace, weaponreplace) \
40         w_prop(id, sn, float,  weaponstart, weaponstart) \
41         w_prop(id, sn, float,  weaponstartoverride, weaponstartoverride) \
42         w_prop(id, sn, float,  weaponthrowable, weaponthrowable)
43
44 #ifdef SVQC
45 HOOK_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP)
46
47 .float dmg;
48 .float dmg_edge;
49 .float dmg_radius;
50 .float dmg_force;
51 .float dmg_power;
52 .float dmg_duration;
53 .float dmg_last;
54 .float hook_refire;
55 .float hook_time_hooked;
56 .float hook_time_fueldecrease;
57 #endif
58 #endif
59 #ifdef IMPLEMENTATION
60 #ifdef SVQC
61
62 void spawnfunc_weapon_hook(void)
63 {SELFPARAM();
64         if(g_grappling_hook) // offhand hook
65         {
66                 startitem_failed = true;
67                 remove(self);
68                 return;
69         }
70         weapon_defaultspawnfunc(WEP_HOOK.m_id);
71 }
72
73 void W_Hook_ExplodeThink(void)
74 {SELFPARAM();
75         float dt, dmg_remaining_next, f;
76
77         dt = time - self.teleport_time;
78         dmg_remaining_next = pow(bound(0, 1 - dt / self.dmg_duration, 1), self.dmg_power);
79
80         f = self.dmg_last - dmg_remaining_next;
81         self.dmg_last = dmg_remaining_next;
82
83         RadiusDamage(self, self.realowner, self.dmg * f, self.dmg_edge * f, self.dmg_radius, self.realowner, world, self.dmg_force * f, self.projectiledeathtype, world);
84         self.projectiledeathtype |= HITTYPE_BOUNCE;
85         //RadiusDamage(self, world, self.dmg * f, self.dmg_edge * f, self.dmg_radius, world, world, self.dmg_force * f, self.projectiledeathtype, world);
86
87         if(dt < self.dmg_duration)
88                 self.nextthink = time + 0.05; // soon
89         else
90                 remove(self);
91 }
92
93 void W_Hook_Explode2(void)
94 {SELFPARAM();
95         self.event_damage = func_null;
96         self.touch = func_null;
97         self.effects |= EF_NODRAW;
98
99         self.think = W_Hook_ExplodeThink;
100         self.nextthink = time;
101         self.dmg = WEP_CVAR_SEC(hook, damage);
102         self.dmg_edge = WEP_CVAR_SEC(hook, edgedamage);
103         self.dmg_radius = WEP_CVAR_SEC(hook, radius);
104         self.dmg_force = WEP_CVAR_SEC(hook, force);
105         self.dmg_power = WEP_CVAR_SEC(hook, power);
106         self.dmg_duration = WEP_CVAR_SEC(hook, duration);
107         self.teleport_time = time;
108         self.dmg_last = 1;
109         self.movetype = MOVETYPE_NONE;
110 }
111
112 void W_Hook_Damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
113 {SELFPARAM();
114         if(self.health <= 0)
115                 return;
116
117         if(!W_CheckProjectileDamage(inflictor.realowner, self.realowner, deathtype, -1)) // no exceptions
118                 return; // g_projectiles_damage says to halt
119
120         self.health = self.health - damage;
121
122         if(self.health <= 0)
123                 W_PrepareExplosionByDamage(self.realowner, W_Hook_Explode2);
124 }
125
126 void W_Hook_Touch2(void)
127 {SELFPARAM();
128         PROJECTILE_TOUCH;
129         self.use();
130 }
131
132 void W_Hook_Attack2(void)
133 {SELFPARAM();
134         entity gren;
135
136         //W_DecreaseAmmo(WEP_CVAR_SEC(hook, ammo)); // WEAPONTODO: Figure out how to handle ammo with hook secondary (gravitybomb)
137         W_SetupShot(self, false, 4, W_Sound("hookbomb_fire"), CH_WEAPON_A, WEP_CVAR_SEC(hook, damage));
138
139         gren = spawn();
140         gren.owner = gren.realowner = self;
141         gren.classname = "hookbomb";
142         gren.bot_dodge = true;
143         gren.bot_dodgerating = WEP_CVAR_SEC(hook, damage);
144         gren.movetype = MOVETYPE_TOSS;
145         PROJECTILE_MAKETRIGGER(gren);
146         gren.projectiledeathtype = WEP_HOOK.m_id | HITTYPE_SECONDARY;
147         setorigin(gren, w_shotorg);
148         setsize(gren, '0 0 0', '0 0 0');
149
150         gren.nextthink = time + WEP_CVAR_SEC(hook, lifetime);
151         gren.think = adaptor_think2use_hittype_splash;
152         gren.use = W_Hook_Explode2;
153         gren.touch = W_Hook_Touch2;
154
155         gren.takedamage = DAMAGE_YES;
156         gren.health = WEP_CVAR_SEC(hook, health);
157         gren.damageforcescale = WEP_CVAR_SEC(hook, damageforcescale);
158         gren.event_damage = W_Hook_Damage;
159         gren.damagedbycontents = true;
160         gren.missile_flags = MIF_SPLASH | MIF_ARC;
161
162         gren.velocity = '0 0 1' * WEP_CVAR_SEC(hook, speed);
163         if(autocvar_g_projectiles_newton_style)
164                 gren.velocity = gren.velocity + self.velocity;
165
166         gren.gravity = WEP_CVAR_SEC(hook, gravity);
167         //W_SetupProjVelocity_Basic(gren); // just falling down!
168
169         gren.angles = '0 0 0';
170         gren.flags = FL_PROJECTILE;
171
172         CSQCProjectile(gren, true, PROJECTILE_HOOKBOMB, true);
173
174         MUTATOR_CALLHOOK(EditProjectile, self, gren);
175 }
176
177 bool W_Hook(int req)
178 {SELFPARAM();
179         float hooked_time_max, hooked_fuel;
180
181         switch(req)
182         {
183                 case WR_AIM:
184                 {
185                         // no bot AI for hook (yet?)
186                         return true;
187                 }
188                 case WR_THINK:
189                 {
190                         if(self.BUTTON_ATCK || self.BUTTON_HOOK)
191                         {
192                                 if(!self.hook)
193                                 if(!(self.hook_state & HOOK_WAITING_FOR_RELEASE))
194                                 if(!(self.hook_state & HOOK_FIRING))
195                                 if(time > self.hook_refire)
196                                 if(weapon_prepareattack(0, -1))
197                                 {
198                                         W_DecreaseAmmo(WEP_CVAR_PRI(hook, ammo));
199                                         self.hook_state |= HOOK_FIRING;
200                                         weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(hook, animtime), w_ready);
201                                 }
202                         }
203
204                         if(self.BUTTON_ATCK2)
205                         {
206                                 if(weapon_prepareattack(1, WEP_CVAR_SEC(hook, refire)))
207                                 {
208                                         W_Hook_Attack2();
209                                         weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(hook, animtime), w_ready);
210                                 }
211                         }
212
213                         if(self.hook)
214                         {
215                                 // if hooked, no bombs, and increase the timer
216                                 self.hook_refire = max(self.hook_refire, time + WEP_CVAR_PRI(hook, refire) * W_WeaponRateFactor());
217
218                                 // hook also inhibits health regeneration, but only for 1 second
219                                 if(!(self.items & IT_UNLIMITED_WEAPON_AMMO))
220                                         self.pauseregen_finished = max(self.pauseregen_finished, time + autocvar_g_balance_pause_fuel_regen);
221                         }
222
223                         if(self.hook && self.hook.state == 1)
224                         {
225                                 hooked_time_max = WEP_CVAR_PRI(hook, hooked_time_max);
226                                 if(hooked_time_max > 0)
227                                 {
228                                         if( time > self.hook_time_hooked + hooked_time_max )
229                                                 self.hook_state |= HOOK_REMOVING;
230                                 }
231
232                                 hooked_fuel = WEP_CVAR_PRI(hook, hooked_ammo);
233                                 if(hooked_fuel > 0)
234                                 {
235                                         if( time > self.hook_time_fueldecrease )
236                                         {
237                                                 if(!(self.items & IT_UNLIMITED_WEAPON_AMMO))
238                                                 {
239                                                         if( self.ammo_fuel >= (time - self.hook_time_fueldecrease) * hooked_fuel )
240                                                         {
241                                                                 W_DecreaseAmmo((time - self.hook_time_fueldecrease) * hooked_fuel);
242                                                                 self.hook_time_fueldecrease = time;
243                                                                 // decrease next frame again
244                                                         }
245                                                         else
246                                                         {
247                                                                 self.ammo_fuel = 0;
248                                                                 self.hook_state |= HOOK_REMOVING;
249                                                                 W_SwitchWeapon_Force(self, w_getbestweapon(self));
250                                                         }
251                                                 }
252                                         }
253                                 }
254                         }
255                         else
256                         {
257                                 self.hook_time_hooked = time;
258                                 self.hook_time_fueldecrease = time + WEP_CVAR_PRI(hook, hooked_time_free);
259                         }
260
261                         if(self.BUTTON_CROUCH)
262                         {
263                                 self.hook_state &= ~HOOK_PULLING;
264                                 if(self.BUTTON_ATCK || self.BUTTON_HOOK)
265                                         self.hook_state &= ~HOOK_RELEASING;
266                                 else
267                                         self.hook_state |= HOOK_RELEASING;
268                         }
269                         else
270                         {
271                                 self.hook_state |= HOOK_PULLING;
272                                 self.hook_state &= ~HOOK_RELEASING;
273
274                                 if(self.BUTTON_ATCK || self.BUTTON_HOOK)
275                                 {
276                                         // already fired
277                                         if(self.hook)
278                                                 self.hook_state |= HOOK_WAITING_FOR_RELEASE;
279                                 }
280                                 else
281                                 {
282                                         self.hook_state |= HOOK_REMOVING;
283                                         self.hook_state &= ~HOOK_WAITING_FOR_RELEASE;
284                                 }
285                         }
286
287                         return true;
288                 }
289                 case WR_INIT:
290                 {
291                         precache_sound(W_Sound("hook_impact")); // done by g_hook.qc
292                         precache_sound(W_Sound("hook_fire"));
293                         precache_sound(W_Sound("hookbomb_fire"));
294                         HOOK_SETTINGS(WEP_SKIP_CVAR, WEP_SET_PROP);
295                         return true;
296                 }
297                 case WR_SETUP:
298                 {
299                         self.hook_state &= ~HOOK_WAITING_FOR_RELEASE;
300                         return true;
301                 }
302                 case WR_CHECKAMMO1:
303                 {
304                         if(self.hook)
305                                 return self.ammo_fuel > 0;
306                         else
307                                 return self.ammo_fuel >= WEP_CVAR_PRI(hook, ammo);
308                 }
309                 case WR_CHECKAMMO2:
310                 {
311                         // infinite ammo for now
312                         return true; // self.ammo_cells >= WEP_CVAR_SEC(hook, ammo); // WEAPONTODO: see above
313                 }
314                 case WR_CONFIG:
315                 {
316                         HOOK_SETTINGS(WEP_CONFIG_WRITE_CVARS, WEP_CONFIG_WRITE_PROPS);
317                         return true;
318                 }
319                 case WR_RESETPLAYER:
320                 {
321                         self.hook_refire = time;
322                         return true;
323                 }
324                 case WR_SUICIDEMESSAGE:
325                 {
326                         return false;
327                 }
328                 case WR_KILLMESSAGE:
329                 {
330                         return WEAPON_HOOK_MURDER;
331                 }
332         }
333         return false;
334 }
335 #endif
336 #ifdef CSQC
337 bool W_Hook(int req)
338 {SELFPARAM();
339         switch(req)
340         {
341                 case WR_IMPACTEFFECT:
342                 {
343                         vector org2;
344                         org2 = w_org + w_backoff * 2;
345                         pointparticles(particleeffectnum(EFFECT_HOOK_EXPLODE), org2, '0 0 0', 1);
346                         if(!w_issilent)
347                                 sound(self, CH_SHOTS, W_Sound("hookbomb_impact"), VOL_BASE, ATTN_NORM);
348
349                         return true;
350                 }
351                 case WR_INIT:
352                 {
353                         precache_sound(W_Sound("hookbomb_impact"));
354                         return true;
355                 }
356                 case WR_ZOOMRETICLE:
357                 {
358                         // no weapon specific image for this weapon
359                         return false;
360                 }
361         }
362         return false;
363 }
364 #endif
365 #endif