1#include "g_hook.qh" 2 3#include "weapons/common.qh" 4#include "weapons/csqcprojectile.qh" 5#include "weapons/weaponsystem.qh" 6#include "weapons/selection.qh" 7#include "weapons/tracing.qh" 8#include "player.qh" 9#include "command/common.qh" 10#include "round_handler.qh" 11#include "../common/state.qh" 12#include "../common/physics/player.qh" 13#include "../common/vehicles/all.qh" 14#include "../common/constants.qh" 15#include "../common/util.qh" 16#include <common/net_linked.qh> 17#include <common/weapons/_all.qh> 18#include "../lib/warpzone/common.qh" 19#include "../lib/warpzone/server.qh" 20 21.int state; 22 23/*============================================ 24 25 Wazat's Xonotic Grappling Hook 26 27 Contact: Wazat1@gmail.com 28 29 30Installation instructions: 31-------------------------- 32 331. Place hook.c in your gamec source directory with the other source files. 34 352. Add this line to the bottom of progs.src: 36 37gamec/hook.c 38 393. Open defs.h and add these lines to the very bottom: 40 41// Wazat's grappling hook 42.entity hook; 43void GrapplingHookFrame(); 44void RemoveGrapplingHook(entity pl); 45void SetGrappleHookBindings(); 46// hook impulses 47const float GRAPHOOK_FIRE = 20; 48const float GRAPHOOK_RELEASE = 21; 49// (note: you can change the hook impulse #'s to whatever you please) 50 514. Open client.c and add this to the top of PutClientInServer(): 52 53 RemoveGrapplingHook(this); // Wazat's Grappling Hook 54 555. Find ClientConnect() (in client.c) and add these lines to the bottom: 56 57 // Wazat's grappling hook 58 SetGrappleHookBindings(); 59 606. Still in client.c, find PlayerPreThink and add this line just above the call to W_WeaponFrame: 61 62 GrapplingHookFrame(); 63 647. Build and test the mod. You'll want to bind a key to "+hook" like this: 65bind ctrl "+hook" 66 67And you should be done! 68 69 70============================================*/ 71 72.float hook_length; 73 74void RemoveGrapplingHooks(entity pl) 75{ 76 if(pl.move_movetype == MOVETYPE_FLY) 77 set_movetype(pl, MOVETYPE_WALK); 78 79 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) 80 { 81 .entity weaponentity = weaponentities[slot]; 82 if(!pl.(weaponentity)) 83 continue; // continue incase other slots exist? 84 if(pl.(weaponentity).hook) 85 delete(pl.(weaponentity).hook); 86 pl.(weaponentity).hook = NULL; 87 } 88 89 //pl.disableclientprediction = false; 90} 91 92void RemoveHook(entity this) 93{ 94 entity player = this.realowner; 95 .entity weaponentity = this.weaponentity_fld; 96 97 if(player.(weaponentity).hook == this) 98 player.(weaponentity).hook = NULL; 99 100 if(player.move_movetype == MOVETYPE_FLY) 101 set_movetype(player, MOVETYPE_WALK); 102 delete(this); 103} 104 105void GrapplingHookReset(entity this) 106{ 107 RemoveHook(this); 108} 109 110void GrapplingHookThink(entity this); 111void GrapplingHook_Stop(entity this) 112{ 113 Send_Effect(EFFECT_HOOK_IMPACT, this.origin, '0 0 0', 1); 114 sound (this, CH_SHOTS, SND_HOOK_IMPACT, VOL_BASE, ATTEN_NORM); 115 116 this.state = 1; 117 setthink(this, GrapplingHookThink); 118 this.nextthink = time; 119 settouch(this, func_null); 120 this.velocity = '0 0 0'; 121 set_movetype(this, MOVETYPE_NONE); 122 this.hook_length = -1; 123} 124 125.vector hook_start, hook_end; 126bool GrapplingHookSend(entity this, entity to, int sf) 127{ 128 WriteHeader(MSG_ENTITY, ENT_CLIENT_HOOK); 129 sf = sf & 0x7F; 130 if(sound_allowed(MSG_BROADCAST, this.realowner)) 131 sf |= 0x80; 132 WriteByte(MSG_ENTITY, sf); 133 if(sf & 1) 134 { 135 WriteByte(MSG_ENTITY, etof(this.realowner)); 136 WriteByte(MSG_ENTITY, weaponslot(this.weaponentity_fld)); 137 } 138 if(sf & 2) 139 { 140 WriteCoord(MSG_ENTITY, this.hook_start.x); 141 WriteCoord(MSG_ENTITY, this.hook_start.y); 142 WriteCoord(MSG_ENTITY, this.hook_start.z); 143 } 144 if(sf & 4) 145 { 146 WriteCoord(MSG_ENTITY, this.hook_end.x); 147 WriteCoord(MSG_ENTITY, this.hook_end.y); 148 WriteCoord(MSG_ENTITY, this.hook_end.z); 149 } 150 return true; 151} 152 153int autocvar_g_grappling_hook_tarzan; 154 155void GrapplingHookThink(entity this) 156{ 157 float spd, dist, minlength, pullspeed, ropestretch, ropeairfriction, rubberforce, newlength, rubberforce_overstretch; 158 vector dir, org, end, v0, dv, v, myorg, vs; 159 .entity weaponentity = this.weaponentity_fld; 160 if(this.realowner.(weaponentity).hook != this) // how did that happen? 161 { 162 error("Owner lost the hook!\n"); 163 return; 164 } 165 if(LostMovetypeFollow(this) || game_stopped || (round_handler_IsActive() && !round_handler_IsRoundStarted()) || ((this.aiment.flags & FL_PROJECTILE) && this.aiment.classname != "nade")) 166 { 167 RemoveHook(this); 168 return; 169 } 170 if(this.aiment) 171 WarpZone_RefSys_AddIncrementally(this, this.aiment); 172 173 this.nextthink = time; 174 175 int s = W_GunAlign(this.realowner.(weaponentity), STAT(GUNALIGN, this.realowner)) - 1; 176 vs = hook_shotorigin[s]; 177 178 makevectors(this.realowner.v_angle); 179 org = this.realowner.origin + this.realowner.view_ofs + v_forward * vs.x + v_right * -vs.y + v_up * vs.z; 180 myorg = WarpZone_RefSys_TransformOrigin(this.realowner, this, org); 181 182 if(this.hook_length < 0) 183 this.hook_length = vlen(myorg - this.origin); 184 185 int tarzan = autocvar_g_grappling_hook_tarzan; 186 entity pull_entity = this.realowner; 187 float velocity_multiplier = 1; 188 MUTATOR_CALLHOOK(GrappleHookThink, this, tarzan, pull_entity, velocity_multiplier); 189 tarzan = M_ARGV(1, int); 190 pull_entity = M_ARGV(2, entity); 191 velocity_multiplier = M_ARGV(3, float); 192 193 if(this.state == 1) 194 { 195 pullspeed = autocvar_g_balance_grapplehook_speed_pull;//2000; 196 // speed the rope is pulled with 197 198 rubberforce = autocvar_g_balance_grapplehook_force_rubber;//2000; 199 // force the rope will use if it is stretched 200 201 rubberforce_overstretch = autocvar_g_balance_grapplehook_force_rubber_overstretch;//1000; 202 // force the rope will use if it is stretched 203 204 minlength = autocvar_g_balance_grapplehook_length_min;//100; 205 // minimal rope length 206 // if the rope goes below this length, it isn't pulled any more 207 208 ropestretch = autocvar_g_balance_grapplehook_stretch;//400; 209 // if the rope is stretched by more than this amount, more rope is 210 // given to you again 211 212 ropeairfriction = autocvar_g_balance_grapplehook_airfriction;//0.2 213 // while hanging on the rope, this friction component will help you a 214 // bit to control the rope 215 216 bool frozen_pulling = (autocvar_g_grappling_hook_tarzan >= 2 && autocvar_g_balance_grapplehook_pull_frozen); 217 218 dir = this.origin - myorg; 219 dist = vlen(dir); 220 dir = normalize(dir); 221 222 if(tarzan) 223 { 224 v = v0 = WarpZone_RefSys_TransformVelocity(pull_entity, this, pull_entity.velocity); 225 226 // first pull the rope... 227 if(this.realowner.(weaponentity).hook_state & HOOK_PULLING) 228 { 229 newlength = this.hook_length; 230 newlength = max(newlength - pullspeed * frametime, minlength); 231 232 if(newlength < dist - ropestretch) // overstretched? 233 { 234 newlength = dist - ropestretch; 235 if(v * dir < 0) // only if not already moving in hook direction 236 v = v + frametime * dir * rubberforce_overstretch; 237 } 238 239 this.hook_length = newlength; 240 } 241 242 if(pull_entity.move_movetype == MOVETYPE_FLY) 243 set_movetype(pull_entity, MOVETYPE_WALK); 244 245 if(this.realowner.(weaponentity).hook_state & HOOK_RELEASING) 246 { 247 newlength = dist; 248 this.hook_length = newlength; 249 } 250 else 251 { 252 // then pull the player 253 spd = bound(0, (dist - this.hook_length) / ropestretch, 1); 254 v = v * (1 - frametime * ropeairfriction); 255 v = v + frametime * dir * spd * rubberforce; 256 257 dv = ((v - v0) * dir) * dir; 258 if(tarzan >= 2) 259 { 260 if(this.aiment.move_movetype == MOVETYPE_WALK || this.aiment.classname == "nade") 261 { 262 entity aim_ent = ((IS_VEHICLE(this.aiment) && this.aiment.owner) ? this.aiment.owner : this.aiment); 263 v = v - dv * 0.5; 264 if((frozen_pulling && STAT(FROZEN, this.aiment)) || !frozen_pulling) 265 { 266 this.aiment.velocity = this.aiment.velocity - dv * 0.5; 267 UNSET_ONGROUND(this.aiment); 268 if(this.aiment.flags & FL_PROJECTILE) 269 UpdateCSQCProjectile(this.aiment); 270 } 271 if(this.aiment.classname == "nade") 272 this.aiment.nextthink = time + autocvar_g_balance_grapplehook_nade_time; // set time after letting go? 273 aim_ent.pusher = this.realowner; 274 aim_ent.pushltime = time + autocvar_g_maxpushtime; 275 aim_ent.istypefrag = PHYS_INPUT_BUTTON_CHAT(aim_ent); 276 } 277 } 278 279 UNSET_ONGROUND(pull_entity); 280 } 281 282 if(!frozen_pulling && !(this.aiment.flags & FL_PROJECTILE)) 283 pull_entity.velocity = WarpZone_RefSys_TransformVelocity(this, pull_entity, v * velocity_multiplier); 284 285 if(frozen_pulling && autocvar_g_balance_grapplehook_pull_frozen == 2 && !STAT(FROZEN, this.aiment)) 286 { 287 RemoveHook(this); 288 return; 289 } 290 } 291 else 292 { 293 end = this.origin - dir*50; 294 dist = vlen(end - myorg); 295 if(dist < 200) 296 spd = dist * (pullspeed / 200); 297 else 298 spd = pullspeed; 299 if(spd < 50) 300 spd = 0; 301 this.realowner.velocity = dir*spd; 302 set_movetype(this.realowner, MOVETYPE_FLY); 303 304 UNSET_ONGROUND(this.realowner); 305 } 306 } 307 308 makevectors(this.angles.x * '-1 0 0' + this.angles.y * '0 1 0'); 309 myorg = WarpZone_RefSys_TransformOrigin(this, this.realowner, this.origin); // + v_forward * (-9); 310 311 if(myorg != this.hook_start) 312 { 313 this.SendFlags |= 2; 314 this.hook_start = myorg; 315 } 316 if(org != this.hook_end) 317 { 318 this.SendFlags |= 4; 319 this.hook_end = org; 320 } 321} 322 323void GrapplingHookTouch(entity this, entity toucher) 324{ 325 if(toucher.move_movetype == MOVETYPE_FOLLOW) 326 return; 327 PROJECTILE_TOUCH(this, toucher); 328 329 GrapplingHook_Stop(this); 330 331 if(toucher) 332 if(toucher.move_movetype != MOVETYPE_NONE) 333 { 334 SetMovetypeFollow(this, toucher); 335 WarpZone_RefSys_BeginAddingIncrementally(this, this.aiment); 336 } 337 338 //this.realowner.disableclientprediction = true; 339} 340 341void GrapplingHook_Damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) 342{ 343 if(this.health <= 0) 344 return; 345 346 if (!W_CheckProjectileDamage(inflictor.realowner, this.realowner, deathtype, -1)) // no exceptions 347 return; // g_balance_projectiledamage says to halt 348 349 this.health = this.health - damage; 350 351 if (this.health <= 0) 352 { 353 if(attacker != this.realowner) 354 { 355 this.realowner.pusher = attacker; 356 this.realowner.pushltime = time + autocvar_g_maxpushtime; 357 this.realowner.istypefrag = PHYS_INPUT_BUTTON_CHAT(this.realowner); 358 } 359 RemoveHook(this); 360 } 361} 362 363void FireGrapplingHook(entity actor, .entity weaponentity) 364{ 365 if(forbidWeaponUse(actor)) return; 366 if(actor.vehicle) return; 367 368 makevectors(actor.v_angle); 369 370 int s = W_GunAlign(actor.(weaponentity), STAT(GUNALIGN, actor)) - 1; 371 vector vs = hook_shotorigin[s]; 372 373 // UGLY WORKAROUND: play this on CH_WEAPON_B so it can't cut off fire sounds 374 sound (actor, CH_WEAPON_B, SND_HOOK_FIRE, VOL_BASE, ATTEN_NORM); 375 vector org = actor.origin + actor.view_ofs + v_forward * vs.x + v_right * -vs.y + v_up * vs.z; 376 377 tracebox(actor.origin + actor.view_ofs, '-3 -3 -3', '3 3 3', org, MOVE_NORMAL, actor); 378 org = trace_endpos; 379 380 Send_Effect(EFFECT_HOOK_MUZZLEFLASH, org, '0 0 0', 1); 381 382 entity missile = WarpZone_RefSys_SpawnSameRefSys(actor); 383 missile.owner = missile.realowner = actor; 384 actor.(weaponentity).hook = missile; 385 missile.weaponentity_fld = weaponentity; 386 missile.reset = GrapplingHookReset; 387 missile.classname = "grapplinghook"; 388 missile.flags = FL_PROJECTILE; 389 IL_PUSH(g_projectiles, missile); 390 IL_PUSH(g_bot_dodge, missile); 391 392 set_movetype(missile, ((autocvar_g_balance_grapplehook_gravity) ? MOVETYPE_TOSS : MOVETYPE_FLY)); 393 PROJECTILE_MAKETRIGGER(missile); 394 395 //setmodel (missile, MDL_HOOK); // precision set below 396 setsize (missile, '-3 -3 -3', '3 3 3'); 397 setorigin(missile, org); 398 399 missile.state = 0; // not latched onto anything 400 401 W_SetupProjVelocity_Explicit(missile, v_forward, v_up, autocvar_g_balance_grapplehook_speed_fly, 0, 0, 0, false); 402 403 missile.angles = vectoangles (missile.velocity); 404 //missile.glow_color = 250; // 244, 250 405 //missile.glow_size = 120; 406 settouch(missile, GrapplingHookTouch); 407 setthink(missile, GrapplingHookThink); 408 missile.nextthink = time; 409 410 missile.effects = /*EF_FULLBRIGHT | EF_ADDITIVE |*/ EF_LOWPRECISION; 411 412 missile.health = autocvar_g_balance_grapplehook_health;//120 413 missile.event_damage = GrapplingHook_Damage; 414 missile.takedamage = DAMAGE_AIM; 415 missile.damageforcescale = 0; 416 missile.damagedbycontents = (autocvar_g_balance_grapplehook_damagedbycontents); 417 if(missile.damagedbycontents) 418 IL_PUSH(g_damagedbycontents, missile); 419 420 missile.hook_start = missile.hook_end = missile.origin; 421 422 Net_LinkEntity(missile, false, 0, GrapplingHookSend); 423} 424 425void GrappleHookInit() 426{ 427 if(g_grappling_hook) 428 { 429 hook_shotorigin[0] = '8 8 -12'; 430 hook_shotorigin[1] = '8 8 -12'; 431 hook_shotorigin[2] = '8 8 -12'; 432 hook_shotorigin[3] = '8 8 -12'; 433 } 434 else 435 { 436 Weapon w = WEP_HOOK; 437 w.wr_init(w); 438 hook_shotorigin[0] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_HOOK.m_id), false, false, 1); 439 hook_shotorigin[1] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_HOOK.m_id), false, false, 2); 440 hook_shotorigin[2] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_HOOK.m_id), false, false, 3); 441 hook_shotorigin[3] = shotorg_adjust_values(CL_Weapon_GetShotOrg(WEP_HOOK.m_id), false, false, 4); 442 } 443} 444