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, this.weaponentity_fld, 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, .entity weaponentity, vector hitloc, vector force)
51 if(GetResource(this, RES_HEALTH) <= 0)
54 if(!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions
55 return; // g_projectiles_damage says to halt
57 SetResourceExplicit(this, RES_HEALTH, GetResource(this, RES_HEALTH));
59 if(GetResource(this, RES_HEALTH) <= 0)
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), WEP_HOOK.m_id | HITTYPE_SECONDARY);
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 gren.weaponentity_fld = weaponentity;
82 setorigin(gren, w_shotorg);
83 setsize(gren, '0 0 0', '0 0 0');
85 gren.nextthink = time + WEP_CVAR_SEC(hook, lifetime);
86 setthink(gren, adaptor_think2use_hittype_splash);
87 gren.use = W_Hook_Explode2_use;
88 settouch(gren, W_Hook_Touch2);
90 gren.takedamage = DAMAGE_YES;
91 SetResourceExplicit(gren, RES_HEALTH, WEP_CVAR_SEC(hook, health));
92 gren.damageforcescale = WEP_CVAR_SEC(hook, damageforcescale);
93 gren.event_damage = W_Hook_Damage;
94 gren.damagedbycontents = true;
95 IL_PUSH(g_damagedbycontents, gren);
96 gren.missile_flags = MIF_SPLASH | MIF_ARC;
98 gren.velocity = '0 0 1' * WEP_CVAR_SEC(hook, speed);
99 if (autocvar_g_projectiles_newton_style)
100 gren.velocity = gren.velocity + actor.velocity;
102 gren.gravity = WEP_CVAR_SEC(hook, gravity);
103 //W_SetupProjVelocity_Basic(gren); // just falling down!
105 gren.angles = '0 0 0';
106 gren.flags = FL_PROJECTILE;
107 IL_PUSH(g_projectiles, gren);
108 IL_PUSH(g_bot_dodge, gren);
110 CSQCProjectile(gren, true, PROJECTILE_HOOKBOMB, true);
112 MUTATOR_CALLHOOK(EditProjectile, actor, gren);
115 METHOD(Hook, wr_think, void(entity thiswep, entity actor, .entity weaponentity, int fire))
118 if(!actor.(weaponentity).hook)
119 if(!(actor.(weaponentity).hook_state & HOOK_WAITING_FOR_RELEASE))
120 if(time > actor.(weaponentity).hook_refire)
121 if(weapon_prepareattack(thiswep, actor, weaponentity, false, -1))
123 W_DecreaseAmmo(thiswep, actor, thiswep.ammo_factor * WEP_CVAR_PRI(hook, ammo), weaponentity);
124 actor.(weaponentity).hook_state |= HOOK_FIRING;
125 actor.(weaponentity).hook_state |= HOOK_WAITING_FOR_RELEASE;
126 weapon_thinkf(actor, weaponentity, WFRAME_FIRE1, WEP_CVAR_PRI(hook, animtime), w_ready);
129 actor.(weaponentity).hook_state |= HOOK_REMOVING;
130 actor.(weaponentity).hook_state &= ~HOOK_WAITING_FOR_RELEASE;
135 if(weapon_prepareattack(thiswep, actor, weaponentity, true, WEP_CVAR_SEC(hook, refire)))
137 W_Hook_Attack2(thiswep, actor, weaponentity);
138 weapon_thinkf(actor, weaponentity, WFRAME_FIRE2, WEP_CVAR_SEC(hook, animtime), w_ready);
142 if(actor.(weaponentity).hook)
144 // if hooked, no bombs, and increase the timer
145 actor.(weaponentity).hook_refire = max(actor.(weaponentity).hook_refire, time + WEP_CVAR_PRI(hook, refire) * W_WeaponRateFactor(actor));
147 // hook also inhibits health regeneration, but only for 1 second
148 if(!(actor.items & IT_UNLIMITED_WEAPON_AMMO))
149 actor.pauseregen_finished = max(actor.pauseregen_finished, time + autocvar_g_balance_pause_fuel_regen);
152 if(actor.(weaponentity).hook && actor.(weaponentity).hook.state == 1)
154 float hooked_time_max = WEP_CVAR_PRI(hook, hooked_time_max);
155 if(hooked_time_max > 0)
157 if( time > actor.(weaponentity).hook_time_hooked + hooked_time_max )
158 actor.(weaponentity).hook_state |= HOOK_REMOVING;
161 float hooked_fuel = thiswep.ammo_factor * WEP_CVAR_PRI(hook, hooked_ammo);
164 if( time > actor.(weaponentity).hook_time_fueldecrease )
166 if(!(actor.items & IT_UNLIMITED_WEAPON_AMMO))
168 if( GetResource(actor, RES_FUEL) >= (time - actor.(weaponentity).hook_time_fueldecrease) * hooked_fuel )
170 W_DecreaseAmmo(thiswep, actor, (time - actor.(weaponentity).hook_time_fueldecrease) * hooked_fuel, weaponentity);
171 actor.(weaponentity).hook_time_fueldecrease = time;
172 // decrease next frame again
176 SetResource(actor, RES_FUEL, 0);
177 actor.(weaponentity).hook_state |= HOOK_REMOVING;
178 if(actor.(weaponentity).m_weapon != WEP_Null) // offhand
179 W_SwitchWeapon_Force(actor, w_getbestweapon(actor, weaponentity), weaponentity);
187 actor.(weaponentity).hook_time_hooked = time;
188 actor.(weaponentity).hook_time_fueldecrease = time + WEP_CVAR_PRI(hook, hooked_time_free);
191 actor.(weaponentity).hook_state = BITSET(actor.(weaponentity).hook_state, HOOK_PULLING, (!PHYS_INPUT_BUTTON_CROUCH(actor) || !autocvar_g_balance_grapplehook_crouchslide));
193 if (actor.(weaponentity).hook_state & HOOK_FIRING)
195 if (actor.(weaponentity).hook)
196 RemoveHook(actor.(weaponentity).hook);
197 FireGrapplingHook(actor, weaponentity);
198 actor.(weaponentity).hook_state &= ~HOOK_FIRING;
199 actor.(weaponentity).hook_refire = max(actor.(weaponentity).hook_refire, time + autocvar_g_balance_grapplehook_refire * W_WeaponRateFactor(actor));
201 else if (actor.(weaponentity).hook_state & HOOK_REMOVING)
203 if (actor.(weaponentity).hook)
204 RemoveHook(actor.(weaponentity).hook);
205 actor.(weaponentity).hook_state &= ~HOOK_REMOVING;
208 METHOD(Hook, wr_setup, void(entity thiswep, entity actor, .entity weaponentity))
210 actor.(weaponentity).hook_state &= ~HOOK_WAITING_FOR_RELEASE;
212 METHOD(Hook, wr_checkammo1, bool(Hook thiswep, entity actor, .entity weaponentity))
214 if (!thiswep.ammo_factor) return true;
216 if(actor.(weaponentity).hook)
217 return GetResource(actor, RES_FUEL) > 0;
219 return GetResource(actor, RES_FUEL) >= WEP_CVAR_PRI(hook, ammo);
221 METHOD(Hook, wr_checkammo2, bool(Hook thiswep, entity actor, .entity weaponentity))
223 // infinite ammo for now
224 return true; // actor.ammo_cells >= WEP_CVAR_SEC(hook, ammo); // WEAPONTODO: see above
226 METHOD(Hook, wr_resetplayer, void(entity thiswep, entity actor))
228 RemoveGrapplingHooks(actor);
229 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
231 .entity weaponentity = weaponentities[slot];
232 actor.(weaponentity).hook_time = 0;
233 actor.(weaponentity).hook_refire = time;
236 METHOD(Hook, wr_killmessage, Notification(entity thiswep))
238 return WEAPON_HOOK_MURDER;
244 METHOD(Hook, wr_impacteffect, void(entity thiswep, entity actor))
247 org2 = w_org + w_backoff * 2;
248 pointparticles(EFFECT_HOOK_EXPLODE, org2, '0 0 0', 1);
250 sound(actor, CH_SHOTS, SND_HOOKBOMB_IMPACT, VOL_BASE, ATTN_NORM);
256 #include <lib/csqcmodel/interpolate.qh>
257 #include <lib/warpzone/common.qh>
259 float autocvar_cl_grapplehook_alpha = 1;
261 void Draw_CylindricLine(vector from, vector to, float thickness, string texture, float aspect, float shift, vector rgb, float theAlpha, float drawflag, vector vieworg);
264 classfield(Hook) .entity HookType; // ENT_CLIENT_*
265 classfield(Hook) .vector origin;
266 classfield(Hook) .vector velocity;
267 classfield(Hook) .float HookSilent;
268 classfield(Hook) .float HookRange;
270 string Draw_GrapplingHook_trace_callback_tex;
271 float Draw_GrapplingHook_trace_callback_rnd;
272 vector Draw_GrapplingHook_trace_callback_rgb;
273 float Draw_GrapplingHook_trace_callback_a;
274 void Draw_GrapplingHook_trace_callback(vector start, vector hit, vector end)
278 vorg = WarpZone_TransformOrigin(WarpZone_trace_transform, view_origin);
279 for(i = 0; i < Draw_GrapplingHook_trace_callback_a; ++i)
280 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);
281 Draw_GrapplingHook_trace_callback_rnd += 0.25 * vlen(hit - start) / 8;
284 classfield(Hook) .float teleport_time;
285 void Draw_GrapplingHook(entity this)
292 float intensity, offset;
294 if(this.teleport_time)
295 if(time > this.teleport_time)
297 sound (this, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, ATTEN_NORM); // safeguard
298 this.teleport_time = 0;
301 InterpolateOrigin_Do(this);
303 int s = W_GunAlign(viewmodels[this.cnt], STAT(GUNALIGN)) - 1;
305 switch(this.HookType)
308 case NET_ENT_CLIENT_HOOK:
309 vs = hook_shotorigin[s];
311 case NET_ENT_CLIENT_ARC_BEAM:
312 vs = lightning_shotorigin[s];
316 if((this.owner.sv_entnum == player_localentnum - 1))
318 switch(this.HookType)
321 case NET_ENT_CLIENT_HOOK:
322 if(autocvar_chase_active)
323 a = csqcplayer.origin + csqcplayer.view_ofs;
325 a = view_origin + view_forward * vs.x + view_right * -vs.y + view_up * vs.z;
328 case NET_ENT_CLIENT_ARC_BEAM:
330 b = view_origin + view_forward * this.HookRange;
332 b = view_origin + view_forward * vlen(this.velocity - this.origin); // honor original length of beam!
333 WarpZone_TraceLine(view_origin, b, MOVE_NORMAL, NULL);
334 b = WarpZone_UnTransformOrigin(WarpZone_trace_transform, trace_endpos);
335 a = view_origin + view_forward * vs.x + view_right * -vs.y + view_up * vs.z;
341 switch(this.HookType)
344 case NET_ENT_CLIENT_HOOK:
348 case NET_ENT_CLIENT_ARC_BEAM:
355 t = entcs_GetTeamColor(this.owner.sv_entnum);
357 switch(this.HookType)
360 case NET_ENT_CLIENT_HOOK:
361 intensity = autocvar_cl_grapplehook_alpha;
365 case NUM_TEAM_1: tex = "particles/hook_red"; rgb = '1 0.3 0.3'; break;
366 case NUM_TEAM_2: tex = "particles/hook_blue"; rgb = '0.3 0.3 1'; break;
367 case NUM_TEAM_3: tex = "particles/hook_yellow"; rgb = '1 1 0.3'; break;
368 case NUM_TEAM_4: tex = "particles/hook_pink"; rgb = '1 0.3 1'; break;
369 default: tex = "particles/hook_white"; rgb = entcs_GetColor(this.sv_entnum - 1); break;
372 case NET_ENT_CLIENT_ARC_BEAM: // todo
373 intensity = bound(0.2, 1 + Noise_Pink(this, frametime) * 1 + Noise_Burst(this, frametime, 0.03) * 0.3, 2);
374 offset = Noise_Brown(this, frametime) * 10;
375 tex = "particles/lgbeam";
380 MUTATOR_CALLHOOK(DrawGrapplingHook, this, tex, rgb, t);
381 tex = M_ARGV(1, string);
382 rgb = M_ARGV(2, vector);
384 Draw_GrapplingHook_trace_callback_tex = tex;
385 Draw_GrapplingHook_trace_callback_rnd = offset;
386 Draw_GrapplingHook_trace_callback_rgb = rgb;
387 Draw_GrapplingHook_trace_callback_a = intensity;
388 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);
389 Draw_GrapplingHook_trace_callback_tex = string_null;
391 atrans = WarpZone_TransformOrigin(WarpZone_trace_transform, a);
393 switch(this.HookType)
396 case NET_ENT_CLIENT_HOOK:
397 if(vdist(trace_endpos - atrans, >, 0.5))
399 setorigin(this, trace_endpos); // hook endpoint!
400 this.angles = vectoangles(trace_endpos - atrans);
401 this.drawmask = MASK_NORMAL;
408 case NET_ENT_CLIENT_ARC_BEAM:
409 setorigin(this, a); // beam origin!
413 switch(this.HookType)
416 case NET_ENT_CLIENT_HOOK:
418 case NET_ENT_CLIENT_ARC_BEAM:
419 pointparticles(EFFECT_ARC_LIGHTNING2, trace_endpos, normalize(atrans - trace_endpos), frametime * intensity); // todo: new effect
424 void Remove_GrapplingHook(entity this)
426 sound (this, CH_SHOTS_SINGLE, SND_Null, VOL_BASE, ATTEN_NORM);
428 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
430 entity wep = viewmodels[slot];
436 NET_HANDLE(ENT_CLIENT_HOOK, bool bIsNew)
438 this.HookType = NET_ENT_CLIENT_HOOK;
442 this.HookSilent = (sf & 0x80);
443 this.iflags = IFLAG_VELOCITY | IFLAG_ORIGIN;
445 InterpolateOrigin_Undo(this);
449 int myowner = ReadByte();
450 int slot = ReadByte();
451 this.owner = playerslots[myowner - 1];
452 this.sv_entnum = myowner;
453 if(myowner == player_localentnum)
454 viewmodels[slot].hook = this;
456 switch(this.HookType)
459 case NET_ENT_CLIENT_HOOK:
462 case NET_ENT_CLIENT_ARC_BEAM:
463 this.HookRange = ReadCoord();
469 this.origin = ReadVector();
470 setorigin(this, this.origin);
474 this.velocity = ReadVector();
477 InterpolateOrigin_Note(this);
479 if(bIsNew || !this.teleport_time)
481 this.draw = Draw_GrapplingHook;
482 IL_PUSH(g_drawables, this);
483 this.entremove = Remove_GrapplingHook;
485 switch(this.HookType)
488 case NET_ENT_CLIENT_HOOK:
490 setmodel(this, MDL_HOOK);
491 this.drawmask = MASK_NORMAL;
493 case NET_ENT_CLIENT_ARC_BEAM:
494 sound (this, CH_SHOTS_SINGLE, SND_LGBEAM_FLY, VOL_BASE, ATTEN_NORM);
499 this.teleport_time = time + 10;
503 // TODO: hook: temporarily transform this.origin for drawing the model along warpzones!