]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/server/g_hook.qc
hook: use MOVETYPE_FOLLOW. You know what for.
[divverent/nexuiz.git] / data / qcsrc / server / g_hook.qc
1 /*============================================
2
3       Wazat's Nexuiz Grappling Hook
4
5         Contact: Wazat1@gmail.com
6
7
8 Installation instructions:
9 --------------------------
10
11 1. Place hook.c in your gamec source directory with the other source files.
12
13 2. Add this line to the bottom of progs.src:
14
15 gamec/hook.c
16
17 3. Open defs.h and add these lines to the very bottom:
18
19 // Wazat's grappling hook
20 .entity         hook;
21 void GrapplingHookFrame();
22 void RemoveGrapplingHook(entity pl);
23 void SetGrappleHookBindings();
24 // hook impulses
25 float GRAPHOOK_FIRE             = 20;
26 float GRAPHOOK_RELEASE          = 21;
27 // (note: you can change the hook impulse #'s to whatever you please)
28
29 4. Open client.c and add this to the top of PutClientInServer():
30
31         RemoveGrapplingHook(self); // Wazat's Grappling Hook
32
33 5. Find ClientConnect() (in client.c) and add these lines to the bottom:
34
35         // Wazat's grappling hook
36         SetGrappleHookBindings();
37
38 6. Still in client.c, find PlayerPreThink and add this line just above the call to W_WeaponFrame:
39
40         GrapplingHookFrame();
41
42 7. Build and test the mod.  You'll want to bind a key to "+hook" like this:
43 bind ctrl "+hook"
44
45 And you should be done!
46
47
48 ============================================*/
49
50 .string aiment_classname;
51 void SetMovetypeFollow(entity ent, entity e)
52 {
53         ent.movetype = MOVETYPE_FOLLOW; // make the hole follow
54         ent.solid = SOLID_NOT; // MOVETYPE_FOLLOW is always non-solid
55         ent.aiment = e; // make the hole follow bmodel
56         ent.punchangle = e.angles; // the original angles of bmodel
57         ent.view_ofs = ent.origin - e.origin; // relative origin
58         ent.v_angle = ent.angles - e.angles; // relative angles
59         ent.aiment_classname = strzone(e.classname);
60 }
61 float LostMovetypeFollow(entity ent)
62 {
63         if(ent.aiment)
64                 if(ent.aiment.classname != ent.aiment_classname)
65                         return 1;
66         return 0;
67 }
68
69 .float rope_length;
70 .float button6_pressed_before;
71
72 void RemoveGrapplingHook(entity pl)
73 {
74         if(pl.hook == world)
75                 return;
76         remove(pl.hook);
77         pl.hook = world;
78         if(pl.movetype == MOVETYPE_FLY)
79                 pl.movetype = MOVETYPE_WALK;
80
81         pl.hook_time = time + 0.0;
82
83         //pl.disableclientprediction = FALSE;
84 }
85
86 void GrapplingHookThink()
87 {
88         float spd, dist, minlength, pullspeed, ropestretch, ropeairfriction, rubberforce, newlength, rubberforce_overstretch;
89         vector dir, org, end;
90         if(self.owner.health <= 0 || self.owner.hook != self)   // how did that happen?
91         {                                                                                                               // well, better fix it anyway
92                 remove(self);
93                 return;
94         }
95         if(LostMovetypeFollow(self))
96         {
97                 remove(self);
98                 return;
99         }
100
101         self.nextthink = time;
102
103         makevectors(self.owner.v_angle);
104         org = self.owner.origin + self.owner.view_ofs + v_forward * 8 - v_right * 8 + v_up * -12;
105
106         if(self.rope_length < 0)
107                 self.rope_length = vlen(org - self.origin);
108
109         if(self.state == 1)
110         {
111                 pullspeed = cvar("g_balance_grapplehook_speed_pull");//2000;
112                 // speed the rope is pulled with
113
114                 rubberforce = cvar("g_balance_grapplehook_force_rubber");//2000;
115                 // force the rope will use if it is stretched
116
117                 rubberforce_overstretch = cvar("g_balance_grapplehook_force_rubber_overstretch");//1000;
118                 // force the rope will use if it is stretched
119
120                 minlength = cvar("g_balance_grapplehook_length_min");//100;
121                 // minimal rope length
122                 // if the rope goes below this length, it isn't pulled any more
123
124                 ropestretch = cvar("g_balance_grapplehook_stretch");//400;
125                 // if the rope is stretched by more than this amount, more rope is
126                 // given to you again
127
128                 ropeairfriction = cvar("g_balance_grapplehook_airfriction");//0.2
129                 // while hanging on the rope, this friction component will help you a
130                 // bit to control the rope
131
132                 dir = self.origin - org;
133                 dist = vlen(dir);
134                 dir = normalize(dir);
135
136                 if(cvar("g_grappling_hook_tarzan"))
137                 {
138                         newlength = self.rope_length;
139
140                         // first pull the rope...
141                         newlength = max(newlength - pullspeed * frametime, minlength);
142
143                         if(newlength < dist - ropestretch) // overstretched?
144                         {
145                                 newlength = dist - ropestretch;
146                                 if(self.owner.velocity * dir < 0) // only if not already moving in hook direction
147                                         self.owner.velocity = self.owner.velocity + frametime * dir * rubberforce_overstretch;
148                         }
149
150                         if(!self.owner.BUTTON_CROUCH) // crouch key = don't pull
151                                 self.rope_length = newlength;
152
153                         // then pull the player
154                         spd = bound(0, (dist - self.rope_length) / ropestretch, 1);
155                         self.owner.velocity = self.owner.velocity * (1 - frametime * ropeairfriction);
156                         self.owner.velocity = self.owner.velocity + frametime * dir * spd * rubberforce;
157                 }
158                 else
159                 {
160                         end = self.origin - dir*50;
161                         dist = vlen(end - org);
162                         if(dist < 200)
163                                 spd = dist * (pullspeed / 200);
164                         else
165                                 spd = pullspeed;
166                         if(spd < 50)
167                                 spd = 0;
168                         self.owner.velocity = dir*spd;
169                         self.owner.movetype = MOVETYPE_FLY;
170                 }
171
172                 self.owner.flags = self.owner.flags - (self.owner.flags & FL_ONGROUND);
173
174                 org = org + dir*50; // get the beam out of the player's eyes
175         }
176
177         makevectors(self.angles_x * '-1 0 0' + self.angles_y * '0 1 0');
178         te_beam(self, self.origin + v_forward * (-9), org);
179 }
180
181 void GrapplingHookTouch (void)
182 {
183         if (other == self.owner)
184                 return;
185         // altered for Nexuiz
186         //else if (pointcontents (self.origin) == CONTENT_SKY)
187         else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
188         {
189                 RemoveGrapplingHook(self.owner);
190                 return;
191         }
192
193         if(other == world)
194         {
195                 vector tic;
196                 tic = self.velocity * sys_ticrate;
197                 tic = tic + normalize(tic) * vlen(self.maxs - self.mins);
198                 traceline(self.origin - tic, self.origin + tic, MOVE_NORMAL, self);
199                 if(trace_fraction >= 1)
200                 {
201                         dprint("Odd... did not hit...?\n");
202                 }
203                 else if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
204                 {
205                         dprint("Detected and prevented the sky-grapple bug.\n");
206                         RemoveGrapplingHook(self.owner);
207                         return;
208                 }
209         }
210
211         pointparticles(particleeffectnum("grapple_impact"), self.origin, '0 0 0', 1);
212         sound (self, CHAN_PROJECTILE, "weapons/hook_impact.wav", VOL_BASE, ATTN_NORM);
213
214         self.state = 1;
215         self.think = GrapplingHookThink;
216         self.nextthink = time;
217         self.touch = SUB_Null;
218         self.velocity = '0 0 0';
219         self.movetype = MOVETYPE_NONE;
220         self.rope_length = -1;
221
222         if(other)
223                 if(other.movetype != MOVETYPE_NONE)
224                         SetMovetypeFollow(self, other);
225
226         //self.owner.disableclientprediction = TRUE;
227 }
228
229 void GrapplingHook_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
230 {
231         if(self.health > 0)
232         {
233                 self.health = self.health - damage;
234                 if (self.health <= 0)
235                 {
236                         if(attacker != self.owner)
237                         {
238                                 self.owner.pusher = attacker;
239                                 self.owner.pushltime = time + cvar("g_maxpushtime");
240                         }
241                         RemoveGrapplingHook(self.owner);
242                 }
243         }
244 }
245
246 void FireGrapplingHook (void)
247 {
248         local entity missile;
249         local vector org;
250
251         if((arena_roundbased && time < warmup) || (time < restart_countdown))
252                 return;
253
254         makevectors(self.v_angle);
255
256         sound (self, CHAN_WEAPON, "weapons/hook_fire.wav", VOL_BASE, ATTN_NORM);
257         org = self.origin + self.view_ofs + v_forward * 8 - v_right * 8 + '0 0 -12';
258         pointparticles(particleeffectnum("grapple_muzzleflash"), org, '0 0 0', 1);
259
260         missile = spawn ();
261         missile.owner = self;
262         self.hook = missile;
263         missile.classname = "grapplinghook";
264
265         missile.movetype = MOVETYPE_FLY;
266         missile.solid = SOLID_BBOX;
267
268         setmodel (missile, "models/hook.md3"); // precision set below
269         setsize (missile, '-3 -3 -3', '3 3 3');
270         setorigin (missile, org);
271
272         missile.state = 0; // not latched onto anything
273
274         missile.velocity = v_forward * cvar("g_balance_grapplehook_speed_fly");
275         W_SetupProjectileVelocity(missile);
276
277         missile.angles = vectoangles (missile.velocity);
278         //missile.glow_color = 250; // 244, 250
279         //missile.glow_size = 120;
280         missile.touch = GrapplingHookTouch;
281         missile.think = GrapplingHookThink;
282         missile.nextthink = time + 0.1;
283
284         missile.effects = /*EF_FULLBRIGHT | EF_ADDITIVE |*/ EF_LOWPRECISION;
285
286         missile.health = cvar("g_balance_grapplehook_health");//120
287         missile.event_damage = GrapplingHook_Damage;
288         missile.takedamage = DAMAGE_AIM;
289         missile.damageforcescale = 0;
290 }
291
292 void GrapplingHookFrame()
293 {
294         // this function has been modified for Nexuiz
295         if (self.BUTTON_HOOK && g_grappling_hook)
296         {
297                 if (!self.hook && self.hook_time <= time && !self.button6_pressed_before)
298                         if (timeoutStatus != 2) //only allow the player to fire the grappling hook if the game is not paused (timeout)
299                                 FireGrapplingHook();
300         }
301         else
302         {
303                 if (self.hook)
304                         RemoveGrapplingHook(self);
305         }
306         self.button6_pressed_before = self.BUTTON_HOOK;
307         /*
308         // if I have no hook or it's not pulling yet, make sure I'm not flying!
309         if((self.hook == world || !self.hook.state) && self.movetype == MOVETYPE_FLY)
310         {
311                 self.movetype = MOVETYPE_WALK;
312         }
313         if(self.impulse == GRAPHOOK_FIRE && self.hook_time <= time && g_grappling_hook)
314         {
315                 // fire hook
316                 FireGrapplingHook();
317                 return;
318         }
319         else if(self.hookimpulse == GRAPHOOK_RELEASE)
320         {
321                 // remove hook, reset movement type
322                 RemoveGrapplingHook(self);
323                 return;
324         }
325         */
326         /*else // make sure the player's movetype is correct
327         {
328                 //if(self.hook == world && self.movetype == MOVETYPE_FLY)
329                 if((self.hook == world || !self.hook.state) && self.movetype == MOVETYPE_FLY)
330                 {
331                         self.movetype = MOVETYPE_WALK;
332                 }
333         }*/
334         // note: The hook entity does the actual pulling
335 }
336
337 void SetGrappleHookBindings()
338 {
339         // this function has been modified for Nexuiz
340         // don't remove these lines! old server or demos coud overwrite the new aliases
341         stuffcmd(self, "alias +hook +button6\n");
342         stuffcmd(self, "alias -hook -button6\n");
343 }