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