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