]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/vehicles/vehicles.qc
058ab783f05a48530b2117d18e10fbc784cbd250
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / vehicles / vehicles.qc
1 float autocvar_g_vehicles_crush_dmg;
2 float autocvar_g_vehicles_crush_force;
3 float autocvar_g_vehicles_delayspawn;
4 float autocvar_g_vehicles_delayspawn_jitter;
5 float autocvar_g_vehicles_allow_flagcarry;
6
7 void vehicles_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force);
8 void vehicles_return();
9 void vehicles_enter();
10 void vehicles_touch();
11 void vehicles_reset_colors();
12 void vehicles_clearrturn();
13 void vehicles_setreturn();
14
15
16 /** AuxiliaryXhair*
17     Send additional points of interest to be drawn, to vehicle owner
18 **/
19 float MAX_AXH = 4;
20 .entity AuxiliaryXhair[MAX_AXH];
21
22 float SendAuxiliaryXhair(entity to, float sf)
23 {
24
25         WriteByte(MSG_ENTITY, ENT_CLIENT_AUXILIARYXHAIR);
26
27         WriteByte(MSG_ENTITY, self.cnt);
28
29         WriteCoord(MSG_ENTITY, self.origin_x);
30         WriteCoord(MSG_ENTITY, self.origin_y);
31         WriteCoord(MSG_ENTITY, self.origin_z);
32
33     WriteByte(MSG_ENTITY, rint(self.colormod_x * 255));
34     WriteByte(MSG_ENTITY, rint(self.colormod_y * 255));
35     WriteByte(MSG_ENTITY, rint(self.colormod_z * 255));
36
37     return TRUE;
38 }
39
40 void UpdateAuxiliaryXhair(entity own, vector loc, vector clr, float axh_id)
41 {
42     entity axh;
43
44     axh_id = bound(0, axh_id, MAX_AXH);
45     axh = own.(AuxiliaryXhair[axh_id]);
46
47     if(axh == world || wasfreed(axh))  // MADNESS? THIS IS QQQQCCCCCCCCC (wasfreed, why do you exsist?)
48     {
49         axh                     = spawn();
50         axh.cnt                 = axh_id;
51         axh.drawonlytoclient    = own;
52         axh.owner               = own;
53         Net_LinkEntity(axh, FALSE, 0, SendAuxiliaryXhair);
54     }
55
56     setorigin(axh, loc);
57     axh.colormod            = clr;
58     axh.SendFlags           = 0x01;
59     own.(AuxiliaryXhair[axh_id]) = axh;
60 }
61
62 /*
63 // SVC_TEMPENTITY based, horrible with even 50 ping. hm.
64 // WriteByte(MSG_ONE, SVC_TEMPENTITY) uses reliable messagess, never use for thinsg that need continous updates.
65 void SendAuxiliaryXhair2(entity own, vector loc, vector clr, float axh_id)
66 {
67         msg_entity = own;
68
69         WriteByte(MSG_ONE, SVC_TEMPENTITY);
70         WriteByte(MSG_ONE, TE_CSQC_AUXILIARYXHAIR);
71
72         WriteByte(MSG_ONE, axh_id);
73
74         WriteCoord(MSG_ONE, loc_x);
75         WriteCoord(MSG_ONE, loc_y);
76         WriteCoord(MSG_ONE, loc_z);
77
78     WriteByte(MSG_ONE, rint(clr_x * 255));
79     WriteByte(MSG_ONE, rint(clr_y * 255));
80     WriteByte(MSG_ONE, rint(clr_z * 255));
81
82 }
83 */
84 // End AuxiliaryXhair
85
86 /**
87     Notifies the client that he enterd a vehicle, and sends
88     realavent data.
89
90     only sends vehicle_id atm (wich is a HUD_* constant, ex. HUD_SPIDERBOT)
91 **/
92 void CSQCVehicleSetup(entity own, float vehicle_id)
93 {
94         msg_entity = own;
95
96         WriteByte(MSG_ONE, SVC_TEMPENTITY);
97         WriteByte(MSG_ONE, TE_CSQC_VEHICLESETUP);
98         WriteByte(MSG_ONE, vehicle_id);
99 }
100
101 /** vehicles_locktarget
102
103     Generic target locking.
104
105     Figure out if what target is "locked" (if any), for missile tracking as such.
106
107     after calling, "if(self.lock_target != world && self.lock_strength == 1)" mean
108     you have a locked in target.
109
110     Exspects a crosshair_trace() or equivalent to be
111     dont before calling.
112
113 **/
114 .entity lock_target;
115 .float  lock_strength;
116 .float  lock_time;
117 .float  lock_soundtime;
118 float   DAMAGE_TARGETDRONE = 10;
119
120 vector targetdrone_getnewspot()
121 {
122
123         vector spot;
124         float i;
125         for(i = 0; i < 100; ++i)
126         {
127                 spot = self.origin + randomvec() * 1024;
128                 tracebox(spot, self.mins, self.maxs, spot, MOVE_NORMAL, self);
129                 if(trace_fraction == 1.0 && trace_startsolid == 0 && trace_allsolid == 0)
130                         return spot;
131         }
132         return self.origin;
133 }
134 void targetdrone_think();
135 void targetdrone_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force);
136 void targetdrone_renwe()
137 {
138         self.think = targetdrone_think;
139         self.nextthink = time + 0.1;
140         setorigin(self, targetdrone_getnewspot());
141         self.health = 200;
142         self.takedamage = DAMAGE_TARGETDRONE;
143         self.event_damage = targetdrone_damage;
144         self.solid = SOLID_BBOX;
145         setmodel(self, "models/runematch/rune.mdl");
146         self.effects = EF_LOWPRECISION;
147         self.scale = 10;
148         self.movetype = MOVETYPE_BOUNCEMISSILE;
149         setsize(self, '-100 -100 -100', '100 100 100');
150
151 }
152 void targetdrone_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
153 {
154         self.health -= damage;
155         if(self.health <= 0)
156         {
157                 pointparticles(particleeffectnum("explosion_medium"), self.origin, '0 0 0', 1);
158
159                 if(!self.cnt)
160                         remove(self);
161                 else
162                 {
163                         self.think = targetdrone_renwe;
164                         self.nextthink = time + 1 + random() * 2;
165                         self.solid = SOLID_NOT;
166                         setmodel(self, "");
167                 }
168         }
169 }
170 entity targetdrone_getfear()
171 {
172         entity fear;
173         float i;
174
175         for(i = 64; i <= 1024; i += 64)
176         {
177                 fear = findradius(self.origin, i);
178                 while(fear)
179                 {
180                         if(fear.bot_dodge)
181                                 return fear;
182
183                         fear = fear.chain;
184                 }
185         }
186
187         return world;
188 }
189 void targetdrone_think()
190 {
191         self.nextthink = time + 0.1;
192
193         if(self.enemy)
194         if(self.enemy.deadflag != DEAD_NO)
195                 self.enemy = targetdrone_getfear();
196
197         if(!self.enemy)
198                 self.enemy = targetdrone_getfear();
199
200         vector newdir;
201
202         if(self.enemy)
203                 newdir = steerlib_push(self.enemy.origin) + randomvec() * 0.75;
204         else
205                 newdir = randomvec() * 0.75;
206
207         newdir = newdir * 0.5 + normalize(self.velocity) * 0.5;
208
209         if(self.enemy)
210                 self.velocity = normalize(newdir) * (500 + (1024 / min(vlen(self.enemy.origin - self.origin), 1024)) * 700);
211         else
212                 self.velocity = normalize(newdir) * 750;
213
214         tracebox(self.origin, self.mins, self.maxs, self.origin + self.velocity * 2, MOVE_NORMAL, self);
215         if(trace_fraction != 1.0)
216                 self.velocity = self.velocity * -1;
217
218         //normalize((normalize(self.velocity) * 0.5 + newdir * 0.5)) * 750;
219 }
220
221 void targetdrone_spawn(vector _where, float _autorenew)
222 {
223         entity drone = spawn();
224         setorigin(drone, _where);
225         drone.think = targetdrone_renwe;
226         drone.nextthink = time + 0.1;
227         drone.cnt = _autorenew;
228 }
229
230 void vehicles_locktarget(float incr, float decr, float _lock_time)
231 {
232     if(self.lock_target && self.lock_target.deadflag != DEAD_NO)
233     {
234         self.lock_target    = world;
235         self.lock_strength  = 0;
236         self.lock_time      = 0;
237     }
238
239     if(self.lock_time > time)
240     {
241         if(self.lock_target)
242         if(self.lock_soundtime < time)
243         {
244             self.lock_soundtime = time + 0.5;
245             play2(self.owner, "vehicles/locked.wav");
246         }
247
248         return;
249     }
250
251     if(trace_ent != world)
252     {
253         if(teamplay && trace_ent.team == self.team)
254             trace_ent = world;
255
256         if(trace_ent.deadflag != DEAD_NO)
257             trace_ent = world;
258
259         if not (trace_ent.vehicle_flags & VHF_ISVEHICLE ||
260                                 trace_ent.turrcaps_flags & TFL_TURRCAPS_ISTURRET ||
261                                 trace_ent.takedamage == DAMAGE_TARGETDRONE)
262             trace_ent = world;
263     }
264
265     if(self.lock_target == world && trace_ent != world)
266         self.lock_target = trace_ent;
267
268     if(self.lock_target && trace_ent == self.lock_target)
269     {
270         if(self.lock_strength != 1 && self.lock_strength + incr >= 1)
271         {
272             play2(self.owner, "vehicles/lock.wav");
273             self.lock_soundtime = time + 0.8;
274         }
275         else if (self.lock_strength != 1 && self.lock_soundtime < time)
276         {
277             play2(self.owner, "vehicles/locking.wav");
278             self.lock_soundtime = time + 0.3;
279         }
280
281     }
282
283     // Have a locking target
284     // Trace hit current target
285     if(trace_ent == self.lock_target && trace_ent != world)
286     {
287         self.lock_strength = min(self.lock_strength + incr, 1);
288         if(self.lock_strength == 1)
289             self.lock_time = time + _lock_time;
290     }
291     else
292     {
293         if(trace_ent)
294             self.lock_strength = max(self.lock_strength - decr * 2, 0);
295         else
296             self.lock_strength = max(self.lock_strength - decr, 0);
297
298         if(self.lock_strength == 0)
299             self.lock_target = world;
300     }
301 }
302
303 #define VEHICLE_UPDATE_PLAYER(fld,vhname) \
304 self.owner.vehicle_##fld = (self.vehicle_##fld / autocvar_g_vehicle_##vhname##_##fld) * 100
305
306 #define vehicles_sweap_collision(orig,vel,dt,acm,mult) \
307 traceline(orig, orig + vel * dt, MOVE_NORMAL, self); \
308 if(trace_fraction != 1) \
309     acm += normalize(self.origin - trace_endpos) * (vlen(vel) * mult)
310
311 // Hover movement support
312 float  force_fromtag_power;
313 float  force_fromtag_normpower;
314 vector force_fromtag_origin;
315 vector vehicles_force_fromtag_hover(string tag_name, float spring_length, float max_power)
316 {
317     force_fromtag_origin = gettaginfo(self, gettagindex(self, tag_name));
318     v_forward  = normalize(v_forward) * -1;
319     traceline(force_fromtag_origin, force_fromtag_origin - (v_forward  * spring_length), MOVE_NORMAL, self);
320
321     force_fromtag_power = (1 - trace_fraction) * max_power;
322     force_fromtag_normpower = force_fromtag_power / max_power;
323
324     return v_forward  * force_fromtag_power;
325 }
326
327 // Experimental hovermode wich uses attraction/repulstion from surface insted of gravity/repulsion
328 // Can possibly be use to move abt any surface (inclusing walls/celings)
329 vector vehicles_force_fromtag_maglev(string tag_name, float spring_length, float max_power)
330 {
331
332     force_fromtag_origin = gettaginfo(self, gettagindex(self, tag_name));
333     v_forward  = normalize(v_forward) * -1;
334     traceline(force_fromtag_origin, force_fromtag_origin - (v_forward  * spring_length), MOVE_NORMAL, self);
335
336     // TODO - this may NOT be compatible with wall/celing movement, unhardcode 0.25 (engine count multiplier)
337     if(trace_fraction == 1.0)
338     {
339         force_fromtag_normpower = -0.25;
340         return '0 0 -200';
341     }
342
343     force_fromtag_power = ((1 - trace_fraction) - trace_fraction) * max_power;
344     force_fromtag_normpower = force_fromtag_power / max_power;
345
346     return v_forward  * force_fromtag_power;
347 }
348
349 // Generic vehile projectile system
350 void vehicles_projectile_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
351 {
352     // Ignore damage from oterh projectiles from my owner (dont mess up volly's)
353     if(inflictor.owner == self.owner)
354         return;
355
356     self.health -= damage;
357     self.velocity += force;
358     if(self.health < 1)
359     {
360         self.takedamage = DAMAGE_NO;
361         self.event_damage = SUB_Null;
362         self.think = self.use;
363         self.nextthink = time;
364     }
365
366 }
367
368 void vehicles_projectile_explode()
369 {
370     if(self.owner && other != world)
371     {
372         if(other == self.owner.vehicle)
373             return;
374
375         if(other == self.owner.vehicle.tur_head)
376             return;
377     }
378
379         PROJECTILE_TOUCH;
380
381         self.event_damage = SUB_Null;
382     RadiusDamage (self, self.realowner, self.shot_dmg, 0, self.shot_radius, self, world, self.shot_force, self.totalfrags, other);
383
384     remove (self);
385 }
386
387 entity vehicles_projectile(string _mzlfx, string _mzlsound,
388                            vector _org, vector _vel,
389                            float _dmg, float _radi, float _force,  float _size,
390                            float _deahtype, float _projtype, float _health,
391                            float _cull, float _clianim)
392 {
393     entity proj;
394
395     proj = spawn();
396
397     PROJECTILE_MAKETRIGGER(proj);
398     setorigin(proj, _org);
399
400     proj.shot_dmg         = _dmg;
401     proj.shot_radius      = _radi;
402     proj.shot_force       = _force;
403     proj.totalfrags       = _deahtype;
404     proj.solid            = SOLID_BBOX;
405     proj.movetype         = MOVETYPE_FLYMISSILE;
406     proj.flags            = FL_PROJECTILE;
407     proj.bot_dodge        = TRUE;
408     proj.bot_dodgerating  = _dmg;
409     proj.velocity         = _vel;
410     proj.touch            = vehicles_projectile_explode;
411     proj.use              = vehicles_projectile_explode;
412     proj.owner            = self;
413     proj.realowner        = self.owner;
414     proj.think            = SUB_Remove;
415     proj.nextthink        = time + 30;
416
417     if(_health)
418     {
419         proj.takedamage       = DAMAGE_AIM;
420         proj.event_damage     = vehicles_projectile_damage;
421         proj.health           = _health;
422     }
423     else
424         proj.flags           = FL_PROJECTILE | FL_NOTARGET;
425
426     if(_mzlsound)
427         sound (self, CH_WEAPON_A, _mzlsound, VOL_BASE, ATTN_NORM);
428
429     if(_mzlfx)
430         pointparticles(particleeffectnum(_mzlfx), proj.origin, proj.velocity, 1);
431
432
433     setsize (proj, '-1 -1 -1' * _size, '1 1 1' * _size);
434
435     CSQCProjectile(proj, _clianim, _projtype, _cull);
436
437     return proj;
438 }
439 // End generic vehile projectile system
440
441 /** vehicles_spawn
442     Exetuted for all vehicles on (re)spawn.
443     Sets defaults for newly spawned units.
444 **/
445 void vehicles_spawn()
446 {
447     dprint("Spawning vehicle: ", self.netname, "\n");
448
449     // De-own & reset
450     self.vehicle_hudmodel.viewmodelforclient = self;
451
452     self.owner              = world;
453     self.touch              = vehicles_touch;
454     self.event_damage       = vehicles_damage;
455     self.iscreature         = TRUE;
456     self.damagedbycontents      = TRUE;
457     self.movetype           = MOVETYPE_WALK;
458     self.solid              = SOLID_SLIDEBOX;
459     self.takedamage         = DAMAGE_AIM;
460         self.deadflag           = DEAD_NO;
461     self.bot_attack         = TRUE;
462     self.flags              = FL_NOTARGET;
463     self.avelocity          = '0 0 0';
464     self.velocity           = '0 0 0';
465
466     // Reset locking
467     self.lock_strength      = 0;
468     self.lock_target        = world;
469     self.misc_bulletcounter = 0;
470
471     // Return to spawn
472     self.angles             = self.pos2;
473     setorigin(self, self.pos1 + '0 0 0');
474     // Show it
475     pointparticles(particleeffectnum("teleport"), self.origin + '0 0 64', '0 0 0', 1);
476
477     vehicles_reset_colors();
478     self.vehicle_spawn();
479 }
480
481 // Better way of determening whats crushable needed! (fl_crushable?)
482 float vehicles_crushable(entity e)
483 {
484     if(e.classname == "player")
485         return TRUE;
486
487     if(e.classname == "monster_zombie")
488         return TRUE;
489
490     return FALSE;
491 }
492
493 void vehilces_impact(float _minspeed, float _speedfac, float _maxpain)
494 {
495     if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
496         return;
497
498     if(self.play_time < time)
499     {
500         float wc = vlen(self.velocity - self.oldvelocity);
501         //dprint("oldvel: ", vtos(self.oldvelocity), "\n");
502         //dprint("vel: ", vtos(self.velocity), "\n");
503         if(_minspeed < wc)
504         {
505             float take = min(_speedfac * wc, _maxpain);
506             Damage (self, world, world, take, DEATH_FALL, self.origin, '0 0 0');
507             self.play_time = time + 0.25;
508
509             //dprint("wc: ", ftos(wc), "\n");
510             //dprint("take: ", ftos(take), "\n");
511         }
512     }
513 }
514
515 .void() vehicle_impact;
516 void vehicles_touch()
517 {
518     // Vehicle currently in use
519     if(self.owner)
520     {
521         if(other != world)
522         if(vehicles_crushable(other))
523         {
524             if(vlen(self.velocity) != 0)
525                 Damage(other, self, self.owner, autocvar_g_vehicles_crush_dmg, DEATH_VHCRUSH, '0 0 0', normalize(other.origin - self.origin) * autocvar_g_vehicles_crush_force);
526
527             return; // Dont do selfdamage when hitting "soft targets".
528         }
529
530         if(self.play_time < time)
531         if(self.vehicle_impact)
532             self.vehicle_impact();
533
534         return;
535     }
536
537     if(other.classname != "player")
538         return;
539
540     if(other.deadflag != DEAD_NO)
541         return;
542
543     if(other.vehicle != world)
544         return;
545
546     // Remove this when bots know how to use vehicles.
547     if (clienttype(other) != CLIENTTYPE_REAL)
548         return;
549
550     vehicles_enter();
551 }
552
553 void vehicles_enter()
554 {
555    // Remove this when bots know how to use vehicles
556     if (clienttype(other) != CLIENTTYPE_REAL)
557         return;
558
559     if(self.phase > time)
560         return;
561
562     if(teamplay)
563     if(self.team)
564     if(self.team != other.team)
565         return;
566
567     RemoveGrapplingHook(other);
568
569     self.vehicle_ammo1   = 0;
570     self.vehicle_ammo2   = 0;
571     self.vehicle_reload1 = 0;
572     self.vehicle_reload2 = 0;
573     self.vehicle_energy  = 0;
574
575     self.owner          = other;
576     self.switchweapon   = other.switchweapon;
577
578     // .viewmodelforclient works better.
579     //self.vehicle_hudmodel.drawonlytoclient = self.owner;
580
581     self.vehicle_hudmodel.viewmodelforclient = self.owner;
582
583     self.event_damage         = vehicles_damage;
584     self.nextthink            = 0;
585     self.owner.angles         = self.angles;
586     self.owner.takedamage     = DAMAGE_NO;
587     self.owner.solid          = SOLID_NOT;
588     self.owner.movetype       = MOVETYPE_NOCLIP;
589     self.owner.alpha          = -1;
590     self.owner.vehicle        = self;
591     self.owner.event_damage   = SUB_Null;
592     self.owner.view_ofs       = '0 0 0';
593     self.colormap             = self.owner.colormap;
594     if(self.tur_head)
595         self.tur_head.colormap    = self.owner.colormap;
596
597     self.owner.hud            = self.hud;
598     self.owner.PlayerPhysplug = self.PlayerPhysplug;
599
600     self.owner.vehicle_ammo1    = self.vehicle_ammo1;
601     self.owner.vehicle_ammo2    = self.vehicle_ammo2;
602     self.owner.vehicle_reload1  = self.vehicle_reload1;
603     self.owner.vehicle_reload2  = self.vehicle_reload2;
604
605     // Cant do this, hides attached objects too.
606     //self.exteriormodeltoclient = self.owner;
607     //self.tur_head.exteriormodeltoclient = self.owner;
608
609     other.flags &~= FL_ONGROUND;
610     self.flags  &~= FL_ONGROUND;
611
612     self.team                 = self.owner.team;
613     self.flags               -= FL_NOTARGET;
614
615     msg_entity = other;
616     WriteByte (MSG_ONE, SVC_SETVIEWPORT);
617     WriteEntity(MSG_ONE, self.vehicle_viewport);
618
619     WriteByte (MSG_ONE, SVC_SETVIEWANGLES);
620     if(self.tur_head)
621     {
622         WriteAngle(MSG_ONE, self.tur_head.angles_x + self.angles_x); // tilt
623         WriteAngle(MSG_ONE, self.tur_head.angles_y + self.angles_y); // yaw
624         WriteAngle(MSG_ONE, 0);                                      // roll
625     }
626     else
627     {
628         WriteAngle(MSG_ONE,  self.angles_x * -1); // tilt
629         WriteAngle(MSG_ONE,  self.angles_y);      // yaw
630         WriteAngle(MSG_ONE,  0);                  // roll
631     }
632
633     vehicles_clearrturn();
634
635     CSQCVehicleSetup(self.owner, self.hud);
636
637     if(other.flagcarried)
638     {
639         if(!autocvar_g_vehicles_allow_flagcarry)
640             DropFlag(other.flagcarried, world, world);
641         else
642         {
643             other.flagcarried.scale = 1;
644             setattachment(other.flagcarried, self, "");
645             setorigin(other, '0 0 96');
646         }
647     }
648
649     self.vehicle_enter();
650     antilag_clear(other);
651 }
652
653 /** vehicles_findgoodexit
654     Locates a valid location for the player to exit the vehicle.
655     Will first try prefer_spot, then up 100 random spots arround the vehicle
656     wich are in direct line of sight and empty enougth to hold a players bbox
657 **/
658 vector vehicles_findgoodexit(vector prefer_spot)
659 {
660     //vector exitspot;
661     float mysize;
662
663     tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, prefer_spot, MOVE_NORMAL, self.owner);
664     if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid)
665         return prefer_spot;
666
667     mysize = vlen(self.maxs - self.mins);
668     float i;
669     vector v, v2;
670     v2 = 0.5 * (self.absmin + self.absmax);
671     for(i = 0; i < 100; ++i)
672     {
673         v = randomvec();
674         v_z = 0;
675         v = v2 + normalize(v) * mysize;
676         tracebox(v2, PL_MIN, PL_MAX, v, MOVE_NORMAL, self.owner);
677         if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid)
678             return v;
679     }
680
681     /*
682     exitspot = (self.origin + '0 0 48') + v_forward * mysize;
683     tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, exitspot, MOVE_NORMAL, self.owner);
684     if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid)
685         return exitspot;
686
687     exitspot = (self.origin + '0 0 48') - v_forward * mysize;
688     tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, exitspot, MOVE_NORMAL, self.owner);
689     if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid)
690         return exitspot;
691
692     exitspot = (self.origin + '0 0 48') + v_right * mysize;
693     tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, exitspot, MOVE_NORMAL, self.owner);
694     if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid)
695         return exitspot;
696
697     exitspot = (self.origin + '0 0 48') - v_right * mysize;
698     tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, exitspot, MOVE_NORMAL, self.owner);
699     if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid)
700         return exitspot;
701     */
702
703     return self.origin;
704 }
705
706 /** vehicles_exit
707     Standarrd vehicle release fucntion.
708     custom code goes in self.vehicle_exit
709 **/
710 void vehicles_exit(float eject)
711 {
712     entity oldself;
713     if(self.flags & FL_CLIENT)
714     {
715         oldself = self;
716         self = self.vehicle;
717     }
718
719         self.flags |= FL_NOTARGET;
720
721     if (self.owner)
722     {
723         msg_entity = self.owner;
724         WriteByte (MSG_ONE, SVC_SETVIEWPORT);
725         WriteEntity( MSG_ONE, self.owner);
726
727         WriteByte (MSG_ONE, SVC_SETVIEWANGLES);
728         WriteAngle(MSG_ONE, 0);                 // pich
729         WriteAngle(MSG_ONE, self.angles_y);     // yaw
730         WriteAngle(MSG_ONE, 0);                 // roll
731
732         setsize(self.owner, PL_MIN,PL_MAX);
733
734         self.owner.takedamage     = DAMAGE_AIM;
735         self.owner.solid          = SOLID_SLIDEBOX;
736         self.owner.movetype       = MOVETYPE_WALK;
737         self.owner.effects        &~= EF_NODRAW;
738         self.owner.alpha          = 1;
739         self.owner.PlayerPhysplug = SUB_Null;
740         self.owner.vehicle        = world;
741         self.owner.view_ofs       = PL_VIEW_OFS;
742         self.owner.event_damage   = PlayerDamage;
743         self.owner.hud            = HUD_NORMAL;
744         self.owner.switchweapon   = self.switchweapon;
745         //self.owner.BUTTON_USE     = 0;
746
747         CSQCVehicleSetup(self.owner, HUD_NORMAL);
748     }
749
750     if(self.deadflag == DEAD_NO)
751         self.avelocity          = '0 0 0';
752
753     self.vehicle_hudmodel.viewmodelforclient = self;
754         self.tur_head.nodrawtoclient             = world;
755     vehicles_setreturn();
756
757     self.phase = time + 1;
758
759     if(!teamplay)
760         self.team = 0;
761     else
762         self.team = self.tur_head.team;
763
764     if(self.owner.flagcarried)
765     {
766         self.owner.flagcarried.scale = 0.6;
767         setattachment(self.owner.flagcarried, self.owner, "");
768         setorigin(self.owner.flagcarried, FLAG_CARRY_POS);
769     }
770
771     sound (self, CH_TRIGGER_SINGLE, "misc/null.wav", 1, ATTN_NORM);
772     self.vehicle_exit(eject);
773     self.owner = world;
774     vehicles_reset_colors();
775
776     if(oldself)
777         self = oldself;
778 }
779
780
781 void vehicles_regen(.float timer, .float regen_field, float field_max, float rpause, float regen, float delta_time)
782 {
783     if(self.regen_field < field_max)
784     if(self.timer + rpause < time)
785     {
786         self.regen_field = min(self.regen_field + regen * delta_time, field_max);
787
788         if(self.owner)
789             self.owner.regen_field = (self.regen_field / field_max) * 100;
790     }
791 }
792
793 void shieldhit_think()
794 {
795     self.alpha -= 0.1;
796     if (self.alpha <= 0)
797     {
798         //setmodel(self, "");
799         self.alpha = -1;
800     }
801     else
802     {
803         self.nextthink = time + 0.1;
804     }
805 }
806
807 void vehicles_painframe()
808 {
809     if(self.owner.vehicle_health <= 50)
810     if(self.pain_frame < time)
811     {
812         float _ftmp;
813         _ftmp = self.owner.vehicle_health / 50;
814         self.pain_frame = time + 0.1 + (random() * 0.5 * _ftmp);
815         pointparticles(particleeffectnum("smoke_small"), (self.origin + (randomvec() * 80)), '0 0 0', 1);
816
817         if(self.vehicle_flags & VHF_DMGSHAKE)
818             self.velocity += randomvec() * 30;
819
820         if(self.vehicle_flags & VHF_DMGROLL)
821             if(self.vehicle_flags & VHF_DMGHEADROLL)
822                 self.tur_head.angles += randomvec();
823             else
824                 self.angles += randomvec();
825
826     }
827 }
828
829 void vehicles_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
830 {
831     self.dmg_time = time;
832
833     if((self.vehicle_flags & VHF_HASSHIELD) && (self.vehicle_shield > 0))
834     {
835         if (wasfreed(self.vehicle_shieldent) || self.vehicle_shieldent == world)
836         {
837             self.vehicle_shieldent = spawn();
838             self.vehicle_shieldent.effects = EF_LOWPRECISION;
839
840             setmodel(self.vehicle_shieldent, "models/vhshield.md3");
841             setattachment(self.vehicle_shieldent, self, "");
842             setorigin(self.vehicle_shieldent, real_origin(self) - self.origin);
843             self.vehicle_shieldent.scale       = 256 / vlen(self.maxs - self.mins);
844             self.vehicle_shieldent.think       = shieldhit_think;
845         }
846
847         self.vehicle_shieldent.colormod    = '1 1 1';
848         self.vehicle_shieldent.alpha       = 0.45;
849         self.vehicle_shieldent.angles      = vectoangles(normalize(hitloc - (self.origin + self.vehicle_shieldent.origin))) - self.angles;
850         self.vehicle_shieldent.nextthink   = time;
851
852         self.vehicle_shield -= damage;
853
854         if(self.vehicle_shield < 0)
855         {
856             self.vehicle_health            -= fabs(self.vehicle_shield);
857             self.vehicle_shieldent.colormod = '2 0 0';
858             self.vehicle_shield             = 0;
859             self.vehicle_shieldent.alpha    = 0.75;
860
861                 if(sound_allowed(MSG_BROADCAST, attacker))
862                 spamsound (self, CH_PAIN, "onslaught/ons_hit2.wav", VOL_BASE, ATTN_NORM);   // FIXME: PLACEHOLDER
863         }
864         else
865                 if(sound_allowed(MSG_BROADCAST, attacker))
866                 spamsound (self, CH_PAIN, "onslaught/electricity_explode.wav", VOL_BASE, ATTN_NORM);  // FIXME: PLACEHOLDER
867
868     }
869     else
870     {
871         self.vehicle_health -= damage;
872
873         if(sound_allowed(MSG_BROADCAST, attacker))
874             spamsound (self, CH_PAIN, "onslaught/ons_hit2.wav", VOL_BASE, ATTN_NORM);  // FIXME: PLACEHOLDER
875     }
876
877     self.velocity += force; // * (vlen(force) / self.mass);
878
879     if(self.vehicle_health <= 0)
880     {
881         if(self.owner)
882             if(self.vehicle_flags & VHF_DEATHEJECT)
883                 vehicles_exit(VHEF_EJECT);
884             else
885                 vehicles_exit(VHEF_RELESE);
886
887         self.vehicle_die();
888         vehicles_setreturn();
889     }
890 }
891
892 void vehicles_clearrturn()
893 {
894     entity ret;
895     // Remove "return helper", if any.
896     ret = findchain(classname, "vehicle_return");
897     while(ret)
898     {
899         if(ret.enemy == self)
900         {
901             ret.classname   = "";
902             ret.think       = SUB_Remove;
903             ret.nextthink   = time + 0.1;
904
905             if(ret.waypointsprite_attached)
906                 WaypointSprite_Kill(ret.waypointsprite_attached);
907
908             return;
909         }
910         ret = ret.chain;
911     }
912 }
913
914 void vehicles_return()
915 {
916     pointparticles(particleeffectnum("teleport"), self.enemy.origin + '0 0 64', '0 0 0', 1);
917
918     self.enemy.think     = vehicles_spawn;
919     self.enemy.nextthink = time;
920
921     if(self.waypointsprite_attached)
922         WaypointSprite_Kill(self.waypointsprite_attached);
923
924     remove(self);
925 }
926
927 void vehicles_showwp_goaway()
928 {
929     if(self.waypointsprite_attached)
930         WaypointSprite_Kill(self.waypointsprite_attached);
931
932     remove(self);
933
934 }
935
936 void vehicles_showwp()
937 {
938     entity oldself;
939     vector rgb;
940
941     if(self.cnt)
942     {
943         self.think      = vehicles_return;
944         self.nextthink  = self.cnt;
945     }
946     else
947     {
948         self.think      = vehicles_return;
949         self.nextthink  = time +1;
950
951         oldself = self;
952         self = spawn();
953         setmodel(self, "null");
954         self.team = oldself.enemy.team;
955         self.enemy = oldself.enemy;
956         setorigin(self, oldself.enemy.pos1);
957
958         self.nextthink = time + 5;
959         self.think = vehicles_showwp_goaway;
960     }
961
962     if(teamplay && self.team)
963             rgb = TeamColor(self.team);
964     else
965             rgb = '1 1 1';
966     WaypointSprite_Spawn("vehicle", 0, 0, self, '0 0 64', world, 0, self, waypointsprite_attached, TRUE, RADARICON_POWERUP, rgb);
967     if(self.waypointsprite_attached)
968     {
969         WaypointSprite_UpdateRule(self.waypointsprite_attached, self.enemy.team, SPRITERULE_DEFAULT);
970         if(oldself == world)
971             WaypointSprite_UpdateBuildFinished(self.waypointsprite_attached, self.nextthink);
972         WaypointSprite_Ping(self.waypointsprite_attached);
973     }
974
975     if(oldself != world)
976         self = oldself;
977 }
978
979 void vehicles_setreturn()
980 {
981     entity ret;
982
983     vehicles_clearrturn();
984
985     ret = spawn();
986     ret.classname   = "vehicle_return";
987     ret.enemy       = self;
988     ret.team        = self.team;
989     ret.think       = vehicles_showwp;
990
991     if(self.deadflag != DEAD_NO)
992     {
993         ret.cnt         = time + self.vehicle_respawntime;
994         ret.nextthink   = min(time + self.vehicle_respawntime, time + self.vehicle_respawntime - 5);
995     }
996     else
997     {
998         ret.nextthink   = min(time + self.vehicle_respawntime, time + self.vehicle_respawntime - 1);
999     }
1000
1001     setmodel(ret, "null");
1002     setorigin(ret, self.pos1 + '0 0 96');
1003
1004 }
1005
1006 void vehicles_configcheck(string  configname, float check_cvar)
1007 {
1008     if(check_cvar == 0)
1009         localcmd(strcat("exec ", configname, "\n"));
1010 }
1011
1012 void vehicles_reset_colors()
1013 {
1014     entity e;
1015     float _effects, _colormap;
1016     vector _glowmod, _colormod;
1017
1018     if(autocvar_g_nodepthtestplayers)
1019         _effects = EF_NODEPTHTEST;
1020
1021     if(autocvar_g_fullbrightplayers)
1022         _effects |= EF_FULLBRIGHT;
1023
1024     if(self.team)
1025         _colormap = 1024 + (self.team - 1) * 17;
1026     else
1027         _colormap = 1024;
1028
1029     _glowmod  = '0 0 0';
1030     _colormod = '0 0 0';
1031
1032     // Find all ents attacked to main model and setup effects, colormod etc.
1033     e = findchainentity(tag_entity, self);
1034     while(e)
1035     {
1036         if(e != self.vehicle_shieldent)
1037         {
1038             e.effects   = _effects; //  | EF_LOWPRECISION;
1039             e.colormod  = _colormod;
1040             e.colormap  = _colormap;
1041             e.alpha     = 1;
1042         }
1043         e = e.chain;
1044     }
1045
1046     self.vehicle_hudmodel.effects  = self.effects  = _effects; // | EF_LOWPRECISION;
1047     self.vehicle_hudmodel.colormod = self.colormod = _colormod;
1048     self.vehicle_hudmodel.colormap = self.colormap = _colormap;
1049     self.vehicle_viewport.effects = (EF_ADDITIVE | EF_DOUBLESIDED | EF_FULLBRIGHT | EF_NODEPTHTEST | EF_NOGUNBOB | EF_NOSHADOW | EF_LOWPRECISION | EF_SELECTABLE | EF_TELEPORT_BIT);
1050
1051     self.alpha     = 1;
1052     self.avelocity = '0 0 0';
1053     self.velocity  = '0 0 0';
1054     self.effects   = _effects;
1055 }
1056
1057 float vehicle_initialize(string  net_name,
1058                          string  bodymodel,
1059                          string  topmodel,
1060                          string  hudmodel,
1061                          string  toptag,
1062                          string  hudtag,
1063                          string  viewtag,
1064                          float   vhud,
1065                          vector  min_s,
1066                          vector  max_s,
1067                          float   nodrop,
1068                          void()  spawnproc,
1069                          float   _respawntime,
1070                          float() physproc,
1071                          void()  enterproc,
1072                          void(float extflag) exitfunc,
1073                          void() dieproc,
1074                          void() thinkproc,
1075                          float  use_csqc)
1076 {
1077     addstat(STAT_HUD, AS_INT,  hud);
1078         addstat(STAT_VEHICLESTAT_HEALTH,  AS_INT, vehicle_health);
1079         addstat(STAT_VEHICLESTAT_SHIELD,  AS_INT, vehicle_shield);
1080         addstat(STAT_VEHICLESTAT_ENERGY,  AS_INT, vehicle_energy);
1081
1082         addstat(STAT_VEHICLESTAT_AMMO1,   AS_INT,   vehicle_ammo1);
1083         addstat(STAT_VEHICLESTAT_RELOAD1, AS_INT, vehicle_reload1);
1084
1085         addstat(STAT_VEHICLESTAT_AMMO2,   AS_INT,   vehicle_ammo2);
1086         addstat(STAT_VEHICLESTAT_RELOAD2, AS_INT, vehicle_reload2);
1087
1088     if(bodymodel == "")
1089         error("vehicles: missing bodymodel!");
1090
1091     if(hudmodel == "")
1092         error("vehicles: missing hudmodel!");
1093
1094     if(net_name == "")
1095         self.netname = self.classname;
1096     else
1097         self.netname = net_name;
1098
1099     if(self.team && !teamplay)
1100         self.team = 0;
1101
1102     self.vehicle_flags |= VHF_ISVEHICLE;
1103
1104     setmodel(self, bodymodel);
1105
1106     self.vehicle_viewport   = spawn();
1107     self.vehicle_hudmodel   = spawn();
1108     self.tur_head           = spawn();
1109     self.tur_head.owner     = self;
1110     self.takedamage         = DAMAGE_AIM;
1111     self.bot_attack         = TRUE;
1112     self.iscreature         = TRUE;
1113     self.damagedbycontents      = TRUE;
1114     self.hud                = vhud;
1115
1116     self.vehicle_die         = dieproc;
1117     self.vehicle_exit        = exitfunc;
1118     self.vehicle_enter       = enterproc;
1119     self.PlayerPhysplug      = physproc;
1120     self.event_damage        = vehicles_damage;
1121     self.touch               = vehicles_touch;
1122     self.think               = vehicles_spawn;
1123     self.nextthink           = time;
1124     self.vehicle_respawntime = _respawntime;
1125     self.vehicle_spawn       = spawnproc;
1126
1127     if(autocvar_g_nodepthtestplayers)
1128         self.effects = self.effects | EF_NODEPTHTEST;
1129
1130     if(autocvar_g_fullbrightplayers)
1131         self.effects = self.effects | EF_FULLBRIGHT;
1132
1133     setmodel(self.vehicle_hudmodel, hudmodel);
1134     setmodel(self.vehicle_viewport, "null");
1135
1136
1137     if(topmodel != "")
1138     {
1139         setmodel(self.tur_head, topmodel);
1140         setattachment(self.tur_head, self, toptag);
1141         setattachment(self.vehicle_hudmodel, self.tur_head, hudtag);
1142         setattachment(self.vehicle_viewport, self.vehicle_hudmodel, viewtag);
1143     }
1144     else
1145     {
1146         setattachment(self.tur_head, self, "");
1147         setattachment(self.vehicle_hudmodel, self, hudtag);
1148         setattachment(self.vehicle_viewport, self.vehicle_hudmodel, viewtag);
1149     }
1150
1151     setsize(self, min_s, max_s);
1152     if not (nodrop)
1153     {
1154         setorigin(self, self.origin);
1155         tracebox(self.origin + '0 0 100', min_s, max_s, self.origin - '0 0 10000', MOVE_WORLDONLY, self);
1156         setorigin(self, trace_endpos);
1157     }
1158
1159     self.pos1 = self.origin;
1160     self.pos2 = self.angles;
1161     self.tur_head.team = self.team;
1162
1163     return TRUE;
1164 }
1165
1166 void bugmenot()
1167 {
1168     self.vehicle_exit       = self.vehicle_exit;
1169     self.vehicle_enter      = self.vehicle_exit;
1170     self.vehicle_die        = self.vehicle_exit;
1171     self.vehicle_spawn      = self.vehicle_exit;
1172     self.AuxiliaryXhair     = self.AuxiliaryXhair;
1173 }