5 void W_Hook_ExplodeThink(entity this)
7 float dt, dmg_remaining_next, f;
9 dt = time - this.teleport_time;
10 dmg_remaining_next = (bound(0, 1 - dt / this.dmg_duration, 1) ** this.dmg_power);
12 f = this.dmg_last - dmg_remaining_next;
13 this.dmg_last = dmg_remaining_next;
15 RadiusDamage(this, this.realowner, this.dmg * f, this.dmg_edge * f, this.dmg_radius, this.realowner, NULL, this.dmg_force * f, this.projectiledeathtype, NULL);
16 this.projectiledeathtype |= HITTYPE_BOUNCE;
17 //RadiusDamage(this, NULL, this.dmg * f, this.dmg_edge * f, this.dmg_radius, NULL, NULL, this.dmg_force * f, this.projectiledeathtype, NULL);
19 if(dt < this.dmg_duration)
20 this.nextthink = time + 0.05; // soon
25 void W_Hook_Explode2(entity this)
27 this.event_damage = func_null;
28 settouch(this, func_null);
29 this.effects |= EF_NODRAW;
31 setthink(this, W_Hook_ExplodeThink);
32 this.nextthink = time;
33 this.dmg = WEP_CVAR_SEC(hook, damage);
34 this.dmg_edge = WEP_CVAR_SEC(hook, edgedamage);
35 this.dmg_radius = WEP_CVAR_SEC(hook, radius);
36 this.dmg_force = WEP_CVAR_SEC(hook, force);
37 this.dmg_power = WEP_CVAR_SEC(hook, power);
38 this.dmg_duration = WEP_CVAR_SEC(hook, duration);
39 this.teleport_time = time;
41 set_movetype(this, MOVETYPE_NONE);
44 void W_Hook_Explode2_use(entity this, entity actor, entity trigger)
46 W_Hook_Explode2(this);
49 void W_Hook_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
54 if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions
55 return; // g_projectiles_damage says to halt
57 this.health = this.health - damage;
60 W_PrepareExplosionByDamage(this, this.realowner, W_Hook_Explode2);
63 void W_Hook_Touch2(entity this, entity toucher)
65 PROJECTILE_TOUCH(this, toucher);
66 this.use(this, NULL, NULL);
69 void W_Hook_Attack2(Weapon thiswep, entity actor, .entity weaponentity)
71 //W_DecreaseAmmo(thiswep, actor, WEP_CVAR_SEC(hook, ammo)); // WEAPONTODO: Figure out how to handle ammo with hook secondary (gravitybomb)
72 W_SetupShot(actor, weaponentity, false, 4, SND_HOOKBOMB_FIRE, CH_WEAPON_A, WEP_CVAR_SEC(hook, damage));
74 entity gren = new(hookbomb);
75 gren.owner = gren.realowner = actor;
76 gren.bot_dodge = true;
77 gren.bot_dodgerating = WEP_CVAR_SEC(hook, damage);
78 set_movetype(gren, MOVETYPE_TOSS);
79 PROJECTILE_MAKETRIGGER(gren);
80 gren.projectiledeathtype = WEP_HOOK.m_id | HITTYPE_SECONDARY;
81 setorigin(gren, w_shotorg);
82 setsize(gren, '0 0 0', '0 0 0');
84 gren.nextthink = time + WEP_CVAR_SEC(hook, lifetime);
85 setthink(gren, adaptor_think2use_hittype_splash);
86 gren.use = W_Hook_Explode2_use;
87 settouch(gren, W_Hook_Touch2);
89 gren.takedamage = DAMAGE_YES;
90 gren.health = WEP_CVAR_SEC(hook, health);
91 gren.damageforcescale = WEP_CVAR_SEC(hook, damageforcescale);
92 gren.event_damage = W_Hook_Damage;
93 gren.damagedbycontents = true;
94 IL_PUSH(g_damagedbycontents, gren);
95 gren.missile_flags = MIF_SPLASH | MIF_ARC;
97 gren.velocity = '0 0 1' * WEP_CVAR_SEC(hook, speed);
98 if (autocvar_g_projectiles_newton_style)
99 gren.velocity = gren.velocity + actor.velocity;
101 gren.gravity = WEP_CVAR_SEC(hook, gravity);
102 //W_SetupProjVelocity_Basic(gren); // just falling down!
104 gren.angles = '0 0 0';
105 gren.flags = FL_PROJECTILE;
106 IL_PUSH(g_projectiles, gren);
107 IL_PUSH(g_bot_dodge, gren);
109 CSQCProjectile(gren, true, PROJECTILE_HOOKBOMB, true);
111 MUTATOR_CALLHOOK(EditProjectile, actor, gren);
114 METHOD(Hook, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
117 if(!actor.(weaponentity).hook)
118 if(!(actor.(weaponentity).hook_state & HOOK_WAITING_FOR_RELEASE))
119 if(time > actor.(weaponentity).hook_refire)
120 if(weapon_prepareattack(thiswep, actor, weaponentity, false, -1))
122 W_DecreaseAmmo(thiswep, actor, thiswep.ammo_factor * WEP_CVAR_PRI(hook, ammo), weaponentity);
123 actor.(weaponentity).hook_state |= HOOK_FIRING;
124 actor.(weaponentity).hook_state |= HOOK_WAITING_FOR_RELEASE;
125 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(hook, animtime), w_ready);
128 actor.(weaponentity).hook_state |= HOOK_REMOVING;
129 actor.(weaponentity).hook_state &= ~HOOK_WAITING_FOR_RELEASE;
134 if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(hook, refire)))
136 W_Hook_Attack2(thiswep, actor, weaponentity);
137 weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(hook, animtime), w_ready);
141 if(actor.(weaponentity).hook)
143 // if hooked, no bombs, and increase the timer
144 actor.(weaponentity).hook_refire = max(actor.(weaponentity).hook_refire, time + WEP_CVAR_PRI(hook, refire) * W_WeaponRateFactor(actor));
146 // hook also inhibits health regeneration, but only for 1 second
147 if(!(actor.items & IT_UNLIMITED_WEAPON_AMMO))
148 actor.pauseregen_finished = max(actor.pauseregen_finished, time + autocvar_g_balance_pause_fuel_regen);
151 if(actor.(weaponentity).hook && actor.(weaponentity).hook.state == 1)
153 float hooked_time_max = WEP_CVAR_PRI(hook, hooked_time_max);
154 if(hooked_time_max > 0)
156 if( time > actor.(weaponentity).hook_time_hooked + hooked_time_max )
157 actor.(weaponentity).hook_state |= HOOK_REMOVING;
160 float hooked_fuel = thiswep.ammo_factor * WEP_CVAR_PRI(hook, hooked_ammo);
163 if( time > actor.(weaponentity).hook_time_fueldecrease )
165 if(!(actor.items & IT_UNLIMITED_WEAPON_AMMO))
167 if( actor.ammo_fuel >= (time - actor.(weaponentity).hook_time_fueldecrease) * hooked_fuel )
169 W_DecreaseAmmo(thiswep, actor, (time - actor.(weaponentity).hook_time_fueldecrease) * hooked_fuel, weaponentity);
170 actor.(weaponentity).hook_time_fueldecrease = time;
171 // decrease next frame again
176 actor.(weaponentity).hook_state |= HOOK_REMOVING;
177 if(actor.(weaponentity).m_weapon != WEP_Null) // offhand
178 W_SwitchWeapon_Force(actor, w_getbestweapon(actor, weaponentity), weaponentity);
186 actor.(weaponentity).hook_time_hooked = time;
187 actor.(weaponentity).hook_time_fueldecrease = time + WEP_CVAR_PRI(hook, hooked_time_free);
190 actor.(weaponentity).hook_state = BITSET(actor.(weaponentity).hook_state, HOOK_PULLING, (!PHYS_INPUT_BUTTON_CROUCH(actor) || !autocvar_g_balance_grapplehook_crouchslide));
192 if (actor.(weaponentity).hook_state & HOOK_FIRING)
194 if (actor.(weaponentity).hook)
195 RemoveHook(actor.(weaponentity).hook);
196 FireGrapplingHook(actor, weaponentity);
197 actor.(weaponentity).hook_state &= ~HOOK_FIRING;
198 actor.(weaponentity).hook_refire = max(actor.(weaponentity).hook_refire, time + autocvar_g_balance_grapplehook_refire * W_WeaponRateFactor(actor));
200 else if (actor.(weaponentity).hook_state & HOOK_REMOVING)
202 if (actor.(weaponentity).hook)
203 RemoveHook(actor.(weaponentity).hook);
204 actor.(weaponentity).hook_state &= ~HOOK_REMOVING;
207 METHOD(Hook, wr_setup, void(entity thiswep, entity actor, .entity weaponentity))
209 actor.(weaponentity).hook_state &= ~HOOK_WAITING_FOR_RELEASE;
211 METHOD(Hook, wr_checkammo1, bool(Hook thiswep, entity actor, .entity weaponentity))
213 if (!thiswep.ammo_factor) return true;
215 if(actor.(weaponentity).hook)
216 return actor.ammo_fuel > 0;
218 return actor.ammo_fuel >= WEP_CVAR_PRI(hook, ammo);
220 METHOD(Hook, wr_checkammo2, bool(Hook thiswep, entity actor, .entity weaponentity))
222 // infinite ammo for now
223 return true; // actor.ammo_cells >= WEP_CVAR_SEC(hook, ammo); // WEAPONTODO: see above
225 METHOD(Hook, wr_resetplayer, void(entity thiswep, entity actor))
227 RemoveGrapplingHooks(actor);
228 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
230 .entity weaponentity = weaponentities[slot];
231 actor.(weaponentity).hook_time = 0;
232 actor.(weaponentity).hook_refire = time;
235 METHOD(Hook, wr_killmessage, Notification(entity thiswep))
237 return WEAPON_HOOK_MURDER;
243 METHOD(Hook, wr_impacteffect, void(entity thiswep, entity actor))
246 org2 = w_org + w_backoff * 2;
247 pointparticles(EFFECT_HOOK_EXPLODE, org2, '0 0 0', 1);
249 sound(actor, CH_SHOTS, SND_HOOKBOMB_IMPACT, VOL_BASE, ATTN_NORM);
255 #include <lib/csqcmodel/interpolate.qh>
256 #include <lib/warpzone/common.qh>
258 float autocvar_cl_grapplehook_alpha = 1;
260 void Draw_CylindricLine(vector from, vector to, float thickness, string texture, float aspect, float shift, vector rgb, float theAlpha, float drawflag, vector vieworg);
263 class(Hook) .entity HookType; // ENT_CLIENT_*
264 class(Hook) .vector origin;
265 class(Hook) .vector velocity;
266 class(Hook) .float HookSilent;
267 class(Hook) .float HookRange;
269 string Draw_GrapplingHook_trace_callback_tex;
270 float Draw_GrapplingHook_trace_callback_rnd;
271 vector Draw_GrapplingHook_trace_callback_rgb;
272 float Draw_GrapplingHook_trace_callback_a;
273 void Draw_GrapplingHook_trace_callback(vector start, vector hit, vector end)
277 vorg = WarpZone_TransformOrigin(WarpZone_trace_transform, view_origin);
278 for(i = 0; i < Draw_GrapplingHook_trace_callback_a; ++i)
279 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);
280 Draw_GrapplingHook_trace_callback_rnd += 0.25 * vlen(hit - start) / 8;
283 class(Hook) .float teleport_time;
284 void Draw_GrapplingHook(entity this)
291 float intensity, offset;
293 if(this.teleport_time)
294 if(time > this.teleport_time)
296 sound (this, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, ATTEN_NORM); // safeguard
297 this.teleport_time = 0;
300 InterpolateOrigin_Do(this);
302 int s = W_GunAlign(viewmodels[this.cnt], STAT(GUNALIGN)) - 1;
304 switch(this.HookType)
307 case NET_ENT_CLIENT_HOOK:
308 vs = hook_shotorigin[s];
310 case NET_ENT_CLIENT_ARC_BEAM:
311 vs = lightning_shotorigin[s];
315 if((this.owner.sv_entnum == player_localentnum - 1))
317 switch(this.HookType)
320 case NET_ENT_CLIENT_HOOK:
321 if(autocvar_chase_active > 0)
322 a = csqcplayer.origin;
324 a = view_origin + view_forward * vs.x + view_right * -vs.y + view_up * vs.z;
327 case NET_ENT_CLIENT_ARC_BEAM:
329 b = view_origin + view_forward * this.HookRange;
331 b = view_origin + view_forward * vlen(this.velocity - this.origin); // honor original length of beam!
332 WarpZone_TraceLine(view_origin, b, MOVE_NORMAL, NULL);
333 b = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos);
334 a = view_origin + view_forward * vs.x + view_right * -vs.y + view_up * vs.z;
340 switch(this.HookType)
343 case NET_ENT_CLIENT_HOOK:
347 case NET_ENT_CLIENT_ARC_BEAM:
354 t = entcs_GetTeamColor(this.owner.sv_entnum);
356 switch(this.HookType)
359 case NET_ENT_CLIENT_HOOK:
360 intensity = autocvar_cl_grapplehook_alpha;
364 case NUM_TEAM_1: tex = "particles/hook_red"; rgb = '1 0.3 0.3'; break;
365 case NUM_TEAM_2: tex = "particles/hook_blue"; rgb = '0.3 0.3 1'; break;
366 case NUM_TEAM_3: tex = "particles/hook_yellow"; rgb = '1 1 0.3'; break;
367 case NUM_TEAM_4: tex = "particles/hook_pink"; rgb = '1 0.3 1'; break;
368 default: tex = "particles/hook_white"; rgb = entcs_GetColor(this.sv_entnum - 1); break;
371 case NET_ENT_CLIENT_ARC_BEAM: // todo
372 intensity = bound(0.2, 1 + Noise_Pink(this, frametime) * 1 + Noise_Burst(this, frametime, 0.03) * 0.3, 2);
373 offset = Noise_Brown(this, frametime) * 10;
374 tex = "particles/lgbeam";
379 MUTATOR_CALLHOOK(DrawGrapplingHook, this, tex, rgb, t);
380 tex = M_ARGV(1, string);
381 rgb = M_ARGV(2, vector);
383 Draw_GrapplingHook_trace_callback_tex = tex;
384 Draw_GrapplingHook_trace_callback_rnd = offset;
385 Draw_GrapplingHook_trace_callback_rgb = rgb;
386 Draw_GrapplingHook_trace_callback_a = intensity;
387 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);
388 Draw_GrapplingHook_trace_callback_tex = string_null;
390 atrans = WarpZone_TransformOrigin(WarpZone_trace_transform, a);
392 switch(this.HookType)
395 case NET_ENT_CLIENT_HOOK:
396 if(vdist(trace_endpos - atrans, >, 0.5))
398 setorigin(this, trace_endpos); // hook endpoint!
399 this.angles = vectoangles(trace_endpos - atrans);
400 this.drawmask = MASK_NORMAL;
407 case NET_ENT_CLIENT_ARC_BEAM:
408 setorigin(this, a); // beam origin!
412 switch(this.HookType)
415 case NET_ENT_CLIENT_HOOK:
417 case NET_ENT_CLIENT_ARC_BEAM:
418 pointparticles(EFFECT_ARC_LIGHTNING2, trace_endpos, normalize(atrans - trace_endpos), frametime * intensity); // todo: new effect
423 void Remove_GrapplingHook(entity this)
425 sound (this, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, ATTEN_NORM);
427 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
429 entity wep = viewmodels[slot];
435 NET_HANDLE(ENT_CLIENT_HOOK, bool bIsNew)
437 this.HookType = NET_ENT_CLIENT_HOOK;
441 this.HookSilent = (sf & 0x80);
442 this.iflags = IFLAG_VELOCITY | IFLAG_ORIGIN;
444 InterpolateOrigin_Undo(this);
448 int myowner = ReadByte();
449 int slot = ReadByte();
450 this.owner = playerslots[myowner - 1];
451 this.sv_entnum = myowner;
452 if(myowner == player_localentnum)
453 viewmodels[slot].hook = this;
455 switch(this.HookType)
458 case NET_ENT_CLIENT_HOOK:
461 case NET_ENT_CLIENT_ARC_BEAM:
462 this.HookRange = ReadCoord();
468 this.origin_x = ReadCoord();
469 this.origin_y = ReadCoord();
470 this.origin_z = ReadCoord();
471 setorigin(this, this.origin);
475 this.velocity_x = ReadCoord();
476 this.velocity_y = ReadCoord();
477 this.velocity_z = ReadCoord();
480 InterpolateOrigin_Note(this);
482 if(bIsNew || !this.teleport_time)
484 this.draw = Draw_GrapplingHook;
485 IL_PUSH(g_drawables, this);
486 this.entremove = Remove_GrapplingHook;
488 switch(this.HookType)
491 case NET_ENT_CLIENT_HOOK:
493 setmodel(this, MDL_HOOK);
494 this.drawmask = MASK_NORMAL;
496 case NET_ENT_CLIENT_ARC_BEAM:
497 sound (this, CH_SHOTS_SINGLE, SND_LGBEAM_FLY, VOL_BASE, ATTEN_NORM);
502 this.teleport_time = time + 10;
506 // TODO: hook: temporarily transform this.origin for drawing the model along warpzones!