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