5 spawnfunc(weapon_hook) { weapon_defaultspawnfunc(this, WEP_HOOK); }
7 void W_Hook_ExplodeThink(entity this)
9 float dt, dmg_remaining_next, f;
11 dt = time - this.teleport_time;
12 dmg_remaining_next = (bound(0, 1 - dt / this.dmg_duration, 1) ** this.dmg_power);
14 f = this.dmg_last - dmg_remaining_next;
15 this.dmg_last = dmg_remaining_next;
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);
21 if(dt < this.dmg_duration)
22 this.nextthink = time + 0.05; // soon
27 void W_Hook_Explode2(entity this)
29 this.event_damage = func_null;
30 settouch(this, func_null);
31 this.effects |= EF_NODRAW;
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;
43 set_movetype(this, MOVETYPE_NONE);
46 void W_Hook_Explode2_use(entity this, entity actor, entity trigger)
48 W_Hook_Explode2(this);
51 void W_Hook_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
56 if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions
57 return; // g_projectiles_damage says to halt
59 this.health = this.health - damage;
62 W_PrepareExplosionByDamage(this, this.realowner, W_Hook_Explode2);
65 void W_Hook_Touch2(entity this, entity toucher)
67 PROJECTILE_TOUCH(this, toucher);
68 this.use(this, NULL, NULL);
71 void W_Hook_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
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));
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');
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);
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;
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;
103 gren.gravity = WEP_CVAR_SEC(hook, gravity);
104 //W_SetupProjVelocity_Basic(gren); // just falling down!
106 gren.angles = '0 0 0';
107 gren.flags = FL_PROJECTILE;
108 IL_PUSH(g_projectiles, gren);
109 IL_PUSH(g_bot_dodge, gren);
111 CSQCProjectile(gren, true, PROJECTILE_HOOKBOMB, true);
113 MUTATOR_CALLHOOK(EditProjectile, actor, gren);
116 METHOD(Hook, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
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))
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);
130 actor.(weaponentity).hook_state |= HOOK_REMOVING;
131 actor.(weaponentity).hook_state &= ~HOOK_WAITING_FOR_RELEASE;
136 if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(hook, refire)))
138 W_Hook_Attack2(thiswep, actor, weaponentity);
139 weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(hook, animtime), w_ready);
143 if(actor.(weaponentity).hook)
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));
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);
153 if(actor.(weaponentity).hook && actor.(weaponentity).hook.state == 1)
155 float hooked_time_max = WEP_CVAR_PRI(hook, hooked_time_max);
156 if(hooked_time_max > 0)
158 if( time > actor.(weaponentity).hook_time_hooked + hooked_time_max )
159 actor.(weaponentity).hook_state |= HOOK_REMOVING;
162 float hooked_fuel = thiswep.ammo_factor * WEP_CVAR_PRI(hook, hooked_ammo);
165 if( time > actor.(weaponentity).hook_time_fueldecrease )
167 if(!(actor.items & IT_UNLIMITED_WEAPON_AMMO))
169 if( actor.ammo_fuel >= (time - actor.(weaponentity).hook_time_fueldecrease) * hooked_fuel )
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
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);
188 actor.(weaponentity).hook_time_hooked = time;
189 actor.(weaponentity).hook_time_fueldecrease = time + WEP_CVAR_PRI(hook, hooked_time_free);
192 actor.(weaponentity).hook_state = BITSET(actor.(weaponentity).hook_state, HOOK_PULLING, (!PHYS_INPUT_BUTTON_CROUCH(actor) || !autocvar_g_balance_grapplehook_crouchslide));
194 if (actor.(weaponentity).hook_state & HOOK_FIRING)
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));
202 else if (actor.(weaponentity).hook_state & HOOK_REMOVING)
204 if (actor.(weaponentity).hook)
205 RemoveHook(actor.(weaponentity).hook);
206 actor.(weaponentity).hook_state &= ~HOOK_REMOVING;
209 METHOD(Hook, wr_setup, void(entity thiswep, entity actor, .entity weaponentity))
211 actor.(weaponentity).hook_state &= ~HOOK_WAITING_FOR_RELEASE;
213 METHOD(Hook, wr_checkammo1, bool(Hook thiswep, entity actor, .entity weaponentity))
215 if (!thiswep.ammo_factor) return true;
217 if(actor.(weaponentity).hook)
218 return actor.ammo_fuel > 0;
220 return actor.ammo_fuel >= WEP_CVAR_PRI(hook, ammo);
222 METHOD(Hook, wr_checkammo2, bool(Hook thiswep, entity actor, .entity weaponentity))
224 // infinite ammo for now
225 return true; // actor.ammo_cells >= WEP_CVAR_SEC(hook, ammo); // WEAPONTODO: see above
227 METHOD(Hook, wr_resetplayer, void(entity thiswep, entity actor))
229 RemoveGrapplingHooks(actor);
230 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
232 .entity weaponentity = weaponentities[slot];
233 actor.(weaponentity).hook_time = 0;
234 actor.(weaponentity).hook_refire = time;
237 METHOD(Hook, wr_killmessage, Notification(entity thiswep))
239 return WEAPON_HOOK_MURDER;
245 METHOD(Hook, wr_impacteffect, void(entity thiswep, entity actor))
248 org2 = w_org + w_backoff * 2;
249 pointparticles(EFFECT_HOOK_EXPLODE, org2, '0 0 0', 1);
251 sound(actor, CH_SHOTS, SND_HOOKBOMB_IMPACT, VOL_BASE, ATTN_NORM);
257 #include <lib/csqcmodel/interpolate.qh>
258 #include <lib/warpzone/common.qh>
260 float autocvar_cl_grapplehook_alpha = 1;
262 void Draw_CylindricLine(vector from, vector to, float thickness, string texture, float aspect, float shift, vector rgb, float theAlpha, float drawflag, vector vieworg);
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;
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)
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;
285 class(Hook) .float teleport_time;
286 void Draw_GrapplingHook(entity this)
293 float intensity, offset;
295 if(this.teleport_time)
296 if(time > this.teleport_time)
298 sound (this, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, ATTEN_NORM); // safeguard
299 this.teleport_time = 0;
302 InterpolateOrigin_Do(this);
304 int s = W_GunAlign(viewmodels[this.cnt], STAT(GUNALIGN)) - 1;
306 switch(this.HookType)
309 case NET_ENT_CLIENT_HOOK:
310 vs = hook_shotorigin[s];
312 case NET_ENT_CLIENT_ARC_BEAM:
313 vs = lightning_shotorigin[s];
317 if((this.owner.sv_entnum == player_localentnum - 1))
319 switch(this.HookType)
322 case NET_ENT_CLIENT_HOOK:
323 if(autocvar_chase_active > 0)
324 a = csqcplayer.origin;
326 a = view_origin + view_forward * vs.x + view_right * -vs.y + view_up * vs.z;
329 case NET_ENT_CLIENT_ARC_BEAM:
331 b = view_origin + view_forward * this.HookRange;
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;
342 switch(this.HookType)
345 case NET_ENT_CLIENT_HOOK:
349 case NET_ENT_CLIENT_ARC_BEAM:
356 t = entcs_GetTeamColor(this.owner.sv_entnum);
358 switch(this.HookType)
361 case NET_ENT_CLIENT_HOOK:
362 intensity = autocvar_cl_grapplehook_alpha;
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;
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";
381 MUTATOR_CALLHOOK(DrawGrapplingHook, this, tex, rgb, t);
382 tex = M_ARGV(1, string);
383 rgb = M_ARGV(2, vector);
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;
392 atrans = WarpZone_TransformOrigin(WarpZone_trace_transform, a);
394 switch(this.HookType)
397 case NET_ENT_CLIENT_HOOK:
398 if(vdist(trace_endpos - atrans, >, 0.5))
400 setorigin(this, trace_endpos); // hook endpoint!
401 this.angles = vectoangles(trace_endpos - atrans);
402 this.drawmask = MASK_NORMAL;
409 case NET_ENT_CLIENT_ARC_BEAM:
410 setorigin(this, a); // beam origin!
414 switch(this.HookType)
417 case NET_ENT_CLIENT_HOOK:
419 case NET_ENT_CLIENT_ARC_BEAM:
420 pointparticles(EFFECT_ARC_LIGHTNING2, trace_endpos, normalize(atrans - trace_endpos), frametime * intensity); // todo: new effect
425 void Remove_GrapplingHook(entity this)
427 sound (this, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, ATTEN_NORM);
429 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
431 entity wep = viewmodels[slot];
437 NET_HANDLE(ENT_CLIENT_HOOK, bool bIsNew)
439 this.HookType = NET_ENT_CLIENT_HOOK;
443 this.HookSilent = (sf & 0x80);
444 this.iflags = IFLAG_VELOCITY | IFLAG_ORIGIN;
446 InterpolateOrigin_Undo(this);
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;
457 switch(this.HookType)
460 case NET_ENT_CLIENT_HOOK:
463 case NET_ENT_CLIENT_ARC_BEAM:
464 this.HookRange = ReadCoord();
470 this.origin_x = ReadCoord();
471 this.origin_y = ReadCoord();
472 this.origin_z = ReadCoord();
473 setorigin(this, this.origin);
477 this.velocity_x = ReadCoord();
478 this.velocity_y = ReadCoord();
479 this.velocity_z = ReadCoord();
482 InterpolateOrigin_Note(this);
484 if(bIsNew || !this.teleport_time)
486 this.draw = Draw_GrapplingHook;
487 IL_PUSH(g_drawables, this);
488 this.entremove = Remove_GrapplingHook;
490 switch(this.HookType)
493 case NET_ENT_CLIENT_HOOK:
495 setmodel(this, MDL_HOOK);
496 this.drawmask = MASK_NORMAL;
498 case NET_ENT_CLIENT_ARC_BEAM:
499 sound (this, CH_SHOTS_SINGLE, SND_LGBEAM_FLY, VOL_BASE, ATTEN_NORM);
504 this.teleport_time = time + 10;
508 // TODO: hook: temporarily transform this.origin for drawing the model along warpzones!