]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/vehicles/sv_vehicles.qc
Merge branch 'master' into Mario/vehicles
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / vehicles / sv_vehicles.qc
1 // =========================
2 //  SVQC Vehicle Properties
3 // =========================
4
5
6 float SendAuxiliaryXhair(entity to, float sf)
7 {
8
9         WriteByte(MSG_ENTITY, ENT_CLIENT_AUXILIARYXHAIR);
10
11         WriteByte(MSG_ENTITY, self.cnt);
12
13         WriteCoord(MSG_ENTITY, self.origin_x);
14         WriteCoord(MSG_ENTITY, self.origin_y);
15         WriteCoord(MSG_ENTITY, self.origin_z);
16
17         WriteByte(MSG_ENTITY, rint(self.colormod_x * 255));
18         WriteByte(MSG_ENTITY, rint(self.colormod_y * 255));
19         WriteByte(MSG_ENTITY, rint(self.colormod_z * 255));
20
21         return TRUE;
22 }
23
24 void UpdateAuxiliaryXhair(entity own, vector loc, vector clr, float axh_id)
25 {
26         if(!IS_REAL_CLIENT(own))
27                 return;
28
29         entity axh;
30
31         axh_id = bound(0, axh_id, MAX_AXH);
32         axh = own.(AuxiliaryXhair[axh_id]);
33
34         if(axh == world || wasfreed(axh))  // MADNESS? THIS IS QQQQCCCCCCCCC (wasfreed, why do you exsist?)
35         {
36                 axh                                      = spawn();
37                 axh.cnt                          = axh_id;
38                 axh.drawonlytoclient    = own;
39                 axh.owner                          = own;
40                 Net_LinkEntity(axh, FALSE, 0, SendAuxiliaryXhair);
41         }
42
43         setorigin(axh, loc);
44         axh.colormod                    = clr;
45         axh.SendFlags              = 0x01;
46         own.(AuxiliaryXhair[axh_id]) = axh;
47 }
48
49 void CSQCVehicleSetup(entity own, float vehicle_id)
50 {
51         if(!IS_REAL_CLIENT(own))
52                 return;
53
54         msg_entity = own;
55
56         WriteByte(MSG_ONE, SVC_TEMPENTITY);
57         WriteByte(MSG_ONE, TE_CSQC_VEHICLESETUP);
58         WriteByte(MSG_ONE, vehicle_id);
59 }
60
61 vector targetdrone_getnewspot()
62 {
63         vector spot;
64         float i;
65         for(i = 0; i < 100; ++i)
66         {
67                 spot = self.origin + randomvec() * 1024;
68                 tracebox(spot, self.mins, self.maxs, spot, MOVE_NORMAL, self);
69                 if(trace_fraction == 1.0 && trace_startsolid == 0 && trace_allsolid == 0)
70                         return spot;
71         }
72         return self.origin;
73 }
74
75 void vehicles_locktarget(float incr, float decr, float _lock_time)
76 {
77         if(self.lock_target && self.lock_target.deadflag != DEAD_NO)
78         {
79                 self.lock_target        = world;
80                 self.lock_strength  = 0;
81                 self.lock_time    = 0;
82         }
83
84         if(self.lock_time > time)
85         {
86                 if(self.lock_target)
87                 if(self.lock_soundtime < time)
88                 {
89                         self.lock_soundtime = time + 0.5;
90                         play2(self.owner, "vehicles/locked.wav");
91                 }
92
93                 return;
94         }
95
96         if(trace_ent != world)
97         {
98                 if(SAME_TEAM(trace_ent, self))
99                         trace_ent = world;
100
101                 if(trace_ent.deadflag != DEAD_NO)
102                         trace_ent = world;
103
104                 if(!((trace_ent.vehicle_flags & VHF_ISVEHICLE) || (trace_ent.turrcaps_flags & TFL_TURRCAPS_ISTURRET)))
105                         trace_ent = world;
106
107                 if(trace_ent.alpha <= 0.5 && trace_ent.alpha)
108                         trace_ent = world; // invisible
109         }
110
111         if(self.lock_target == world && trace_ent != world)
112                 self.lock_target = trace_ent;
113
114         if(self.lock_target && trace_ent == self.lock_target)
115         {
116                 if(self.lock_strength != 1 && self.lock_strength + incr >= 1)
117                 {
118                         play2(self.owner, "vehicles/lock.wav");
119                         self.lock_soundtime = time + 0.8;
120                 }
121                 else if (self.lock_strength != 1 && self.lock_soundtime < time)
122                 {
123                         play2(self.owner, "vehicles/locking.wav");
124                         self.lock_soundtime = time + 0.3;
125                 }
126         }
127
128         // Have a locking target
129         // Trace hit current target
130         if(trace_ent == self.lock_target && trace_ent != world)
131         {
132                 self.lock_strength = min(self.lock_strength + incr, 1);
133                 if(self.lock_strength == 1)
134                         self.lock_time = time + _lock_time;
135         }
136         else
137         {
138                 if(trace_ent)
139                         self.lock_strength = max(self.lock_strength - decr * 2, 0);
140                 else
141                         self.lock_strength = max(self.lock_strength - decr, 0);
142
143                 if(self.lock_strength == 0)
144                         self.lock_target = world;
145         }
146 }
147
148 vector vehicles_force_fromtag_hover(string tag_name, float spring_length, float max_power)
149 {
150         force_fromtag_origin = gettaginfo(self, gettagindex(self, tag_name));
151         v_forward  = normalize(v_forward) * -1;
152         traceline(force_fromtag_origin, force_fromtag_origin - (v_forward  * spring_length), MOVE_NORMAL, self);
153
154         force_fromtag_power = (1 - trace_fraction) * max_power;
155         force_fromtag_normpower = force_fromtag_power / max_power;
156
157         return v_forward  * force_fromtag_power;
158 }
159
160 vector vehicles_force_fromtag_maglev(string tag_name, float spring_length, float max_power)
161 {
162
163         force_fromtag_origin = gettaginfo(self, gettagindex(self, tag_name));
164         v_forward  = normalize(v_forward) * -1;
165         traceline(force_fromtag_origin, force_fromtag_origin - (v_forward  * spring_length), MOVE_NORMAL, self);
166
167         // TODO - this may NOT be compatible with wall/celing movement, unhardcode 0.25 (engine count multiplier)
168         if(trace_fraction == 1.0)
169         {
170                 force_fromtag_normpower = -0.25;
171                 return '0 0 -200';
172         }
173
174         force_fromtag_power = ((1 - trace_fraction) - trace_fraction) * max_power;
175         force_fromtag_normpower = force_fromtag_power / max_power;
176
177         return v_forward  * force_fromtag_power;
178 }
179
180 // projectile handling
181 void vehicles_projectile_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
182 {
183         // Ignore damage from oterh projectiles from my owner (dont mess up volly's)
184         if(inflictor.owner == self.owner)
185                 return;
186
187         self.health -= damage;
188         self.velocity += force;
189         if(self.health < 1)
190         {
191                 self.takedamage = DAMAGE_NO;
192                 self.event_damage = func_null;
193                 self.think = self.use;
194                 self.nextthink = time;
195         }
196 }
197
198 void vehicles_projectile_explode()
199 {
200         if(self.owner && other != world)
201         {
202                 if(other == self.owner.vehicle)
203                         return;
204
205                 if(other == self.owner.vehicle.tur_head)
206                         return;
207         }
208
209         PROJECTILE_TOUCH;
210
211         self.event_damage = func_null;
212         RadiusDamage (self, self.realowner, self.shot_dmg, 0, self.shot_radius, self, self.shot_force, self.totalfrags, other);
213
214         remove (self);
215 }
216
217 entity vehicles_projectile(string _mzlfx, string _mzlsound,
218                                                    vector _org, vector _vel,
219                                                    float _dmg, float _radi, float _force,  float _size,
220                                                    float _deahtype, float _projtype, float _health,
221                                                    float _cull, float _clianim, entity _owner)
222 {
223         entity proj;
224
225         proj = spawn();
226
227         PROJECTILE_MAKETRIGGER(proj);
228         setorigin(proj, _org);
229
230         proj.shot_dmg            = _dmg;
231         proj.shot_radius          = _radi;
232         proj.shot_force    = _force;
233         proj.totalfrags    = _deahtype;
234         proj.solid                      = SOLID_BBOX;
235         proj.movetype            = MOVETYPE_FLYMISSILE;
236         proj.flags                      = FL_PROJECTILE;
237         proj.bot_dodge          = TRUE;
238         proj.bot_dodgerating  = _dmg;
239         proj.velocity            = _vel;
240         proj.touch                      = vehicles_projectile_explode;
241         proj.use                          = vehicles_projectile_explode;
242         proj.owner                      = self;
243         proj.realowner          = _owner;
244         proj.think                      = SUB_Remove;
245         proj.nextthink          = time + 30;
246
247         if(_health)
248         {
249                 proj.takedamage    = DAMAGE_AIM;
250                 proj.event_damage        = vehicles_projectile_damage;
251                 proj.health                = _health;
252         }
253         else
254                 proj.flags                 = FL_PROJECTILE | FL_NOTARGET;
255
256         if(_mzlsound)
257                 sound (self, CH_WEAPON_A, _mzlsound, VOL_BASE, ATTEN_NORM);
258
259         if(_mzlfx)
260                 pointparticles(particleeffectnum(_mzlfx), proj.origin, proj.velocity, 1);
261
262
263         setsize (proj, '-1 -1 -1' * _size, '1 1 1' * _size);
264
265         CSQCProjectile(proj, _clianim, _projtype, _cull);
266
267         return proj;
268 }
269
270 void vehicles_gib_explode()
271 {
272         sound (self, CH_SHOTS, "weapons/rocket_impact.wav", VOL_BASE, ATTEN_NORM);
273         pointparticles(particleeffectnum("explosion_small"), randomvec() * 80 + (self.origin + '0 0 100'), '0 0 0', 1);
274         remove(self);
275 }
276
277 void vehicles_gib_think()
278 {
279         self.alpha -= 0.1;
280         if(self.cnt >= time)
281                 remove(self);
282         else
283                 self.nextthink = time + 0.1;
284 }
285
286 entity vehicle_tossgib(entity _template, vector _vel, string _tag, float _burn, float _explode, float _maxtime, vector _rot)
287 {
288         entity _gib = spawn();
289         setmodel(_gib, _template.model);
290         setorigin(_gib, gettaginfo(self, gettagindex(self, _tag)));
291         _gib.velocity = _vel;
292         _gib.movetype = MOVETYPE_TOSS;
293         _gib.solid = SOLID_CORPSE;
294         _gib.colormod = '-0.5 -0.5 -0.5';
295         _gib.effects = EF_LOWPRECISION;
296         _gib.avelocity = _rot;
297
298         if(_burn)
299                 _gib.effects |= EF_FLAME;
300
301         if(_explode)
302         {
303                 _gib.think = vehicles_gib_explode;
304                 _gib.nextthink = time + random() * _explode;
305                 _gib.touch = vehicles_gib_explode;
306         }
307         else
308         {
309                 _gib.cnt = time + _maxtime;
310                 _gib.think = vehicles_gib_think;
311                 _gib.nextthink = time + _maxtime - 1;
312                 _gib.alpha = 1;
313         }
314         return _gib;
315 }
316
317 float vehicle_addplayerslot(    entity _owner,
318                                                                 entity _slot,
319                                                                 float _hud,
320                                                                 string _hud_model,
321                                                                 float() _framefunc,
322                                                                 void(float) _exitfunc, float() _enterfunc)
323 {
324         if(!(_owner.vehicle_flags & VHF_MULTISLOT))
325                 _owner.vehicle_flags |= VHF_MULTISLOT;
326
327         _slot.PlayerPhysplug = _framefunc;
328         _slot.vehicle_exit = _exitfunc;
329         _slot.vehicle_enter = _enterfunc;
330         _slot.hud = _hud;
331         _slot.vehicle_flags = VHF_PLAYERSLOT;
332         _slot.vehicle_viewport = spawn();
333         _slot.vehicle_hudmodel = spawn();
334         _slot.vehicle_hudmodel.viewmodelforclient = _slot;
335         _slot.vehicle_viewport.effects = (EF_ADDITIVE | EF_DOUBLESIDED | EF_FULLBRIGHT | EF_NODEPTHTEST | EF_NOGUNBOB | EF_NOSHADOW | EF_LOWPRECISION | EF_SELECTABLE | EF_TELEPORT_BIT);
336
337         setmodel(_slot.vehicle_hudmodel, _hud_model);
338         setmodel(_slot.vehicle_viewport, "null");
339
340         setattachment(_slot.vehicle_hudmodel, _slot, "");
341         setattachment(_slot.vehicle_viewport, _slot.vehicle_hudmodel, "");
342
343         return TRUE;
344 }
345
346 vector vehicle_aimturret(entity _vehic, vector _target, entity _turrret, string _tagname,
347                                                  float _pichlimit_min, float _pichlimit_max,
348                                                  float _rotlimit_min, float _rotlimit_max, float _aimspeed)
349 {
350         vector vtmp, vtag;
351         float ftmp;
352         vtag = gettaginfo(_turrret, gettagindex(_turrret, _tagname));
353         vtmp = vectoangles(normalize(_target - vtag));
354         vtmp = AnglesTransform_ToAngles(AnglesTransform_LeftDivide(AnglesTransform_FromAngles(_vehic.angles), AnglesTransform_FromAngles(vtmp))) - _turrret.angles;
355         vtmp = AnglesTransform_Normalize(vtmp, TRUE);
356         ftmp = _aimspeed * frametime;
357         vtmp_y = bound(-ftmp, vtmp_y, ftmp);
358         vtmp_x = bound(-ftmp, vtmp_x, ftmp);
359         _turrret.angles_y = bound(_rotlimit_min, _turrret.angles_y + vtmp_y, _rotlimit_max);
360         _turrret.angles_x = bound(_pichlimit_min, _turrret.angles_x + vtmp_x, _pichlimit_max);
361         return vtag;
362 }
363
364 void vehicles_reset_colors()
365 {
366         entity e;
367         float _effects = 0, _colormap;
368         vector _glowmod, _colormod;
369
370         if(autocvar_g_nodepthtestplayers)
371                 _effects |= EF_NODEPTHTEST;
372
373         if(autocvar_g_fullbrightplayers)
374                 _effects |= EF_FULLBRIGHT;
375
376         if(self.team)
377                 _colormap = 1024 + (self.team - 1) * 17;
378         else
379                 _colormap = 1024;
380
381         _glowmod  = '0 0 0';
382         _colormod = '0 0 0';
383
384         // Find all ents attacked to main model and setup effects, colormod etc.
385         e = findchainentity(tag_entity, self);
386         while(e)
387         {
388                 if(e != self.vehicle_shieldent)
389                 {
390                         e.effects   = _effects; //  | EF_LOWPRECISION;
391                         e.colormod  = _colormod;
392                         e.colormap  = _colormap;
393                         e.alpha  = 1;
394                 }
395                 e = e.chain;
396         }
397         // Also check head tags
398         e = findchainentity(tag_entity, self.tur_head);
399         while(e)
400         {
401                 if(e != self.vehicle_shieldent)
402                 {
403                         e.effects   = _effects; //  | EF_LOWPRECISION;
404                         e.colormod  = _colormod;
405                         e.colormap  = _colormap;
406                         e.alpha  = 1;
407                 }
408                 e = e.chain;
409         }
410
411         self.vehicle_hudmodel.effects  = self.effects  = _effects; // | EF_LOWPRECISION;
412         self.vehicle_hudmodel.colormod = self.colormod = _colormod;
413         self.vehicle_hudmodel.colormap = self.colormap = _colormap;
414         self.vehicle_viewport.effects = (EF_ADDITIVE | EF_DOUBLESIDED | EF_FULLBRIGHT | EF_NODEPTHTEST | EF_NOGUNBOB | EF_NOSHADOW | EF_LOWPRECISION | EF_SELECTABLE | EF_TELEPORT_BIT);
415
416         self.alpha       = 1;
417         self.avelocity = '0 0 0';
418         self.velocity  = '0 0 0';
419         self.effects   = _effects;
420 }
421
422 void vehicles_clearreturn(entity veh)
423 {
424         entity ret;
425         // Remove "return helper", if any.
426         ret = findchain(classname, "vehicle_return");
427         while(ret)
428         {
429                 if(ret.wp00 == veh)
430                 {
431                         ret.classname   = "";
432                         ret.think          = SUB_Remove;
433                         ret.nextthink   = time + 0.1;
434
435                         if(ret.waypointsprite_attached)
436                                 WaypointSprite_Kill(ret.waypointsprite_attached);
437
438                         return;
439                 }
440                 ret = ret.chain;
441         }
442 }
443
444 void vehicles_spawn();
445 void vehicles_return()
446 {
447         pointparticles(particleeffectnum("teleport"), self.wp00.origin + '0 0 64', '0 0 0', 1);
448
449         self.wp00.think  = vehicles_spawn;
450         self.wp00.nextthink = time;
451
452         if(self.waypointsprite_attached)
453                 WaypointSprite_Kill(self.waypointsprite_attached);
454
455         remove(self);
456 }
457
458 void vehicles_showwp_goaway()
459 {
460         if(self.waypointsprite_attached)
461                 WaypointSprite_Kill(self.waypointsprite_attached);
462
463         remove(self);
464
465 }
466
467 void vehicles_showwp()
468 {
469         entity oldself = world;
470         vector rgb;
471
472         if(self.cnt)
473         {
474                 self.think        = vehicles_return;
475                 self.nextthink  = self.cnt;
476         }
477         else
478         {
479                 self.think        = vehicles_return;
480                 self.nextthink  = time +1;
481
482                 oldself = self;
483                 self = spawn();
484                 setmodel(self, "null");
485                 self.team = oldself.wp00.team;
486                 self.wp00 = oldself.wp00;
487                 setorigin(self, oldself.wp00.pos1);
488
489                 self.nextthink = time + 5;
490                 self.think = vehicles_showwp_goaway;
491         }
492
493         if(teamplay && self.team)
494                 rgb = Team_ColorRGB(self.team);
495         else
496                 rgb = '1 1 1';
497         WaypointSprite_Spawn("vehicle", 0, 0, self, '0 0 64', world, 0, self, waypointsprite_attached, TRUE, RADARICON_POWERUP, rgb);
498         if(self.waypointsprite_attached)
499         {
500                 WaypointSprite_UpdateRule(self.waypointsprite_attached, self.wp00.team, SPRITERULE_DEFAULT);
501                 if(oldself == world)
502                         WaypointSprite_UpdateBuildFinished(self.waypointsprite_attached, self.nextthink);
503                 WaypointSprite_Ping(self.waypointsprite_attached);
504         }
505
506         if(oldself != world)
507                 self = oldself;
508 }
509
510 void vehicles_setreturn(entity veh)
511 {
512         entity ret;
513
514         vehicles_clearreturn(veh);
515
516         ret = spawn();
517         ret.classname   = "vehicle_return";
518         ret.wp00           = veh;
519         ret.team                = veh.team;
520         ret.think          = vehicles_showwp;
521
522         if(veh.deadflag != DEAD_NO)
523         {
524                 ret.cnt          = time + veh.respawntime;
525                 ret.nextthink   = min(time + veh.respawntime, time + veh.respawntime - 5);
526         }
527         else
528         {
529                 ret.nextthink   = min(time + veh.respawntime, time + veh.respawntime - 1);
530         }
531
532         setmodel(ret, "null");
533         setorigin(ret, veh.pos1 + '0 0 96');
534
535 }
536
537 void vehicle_use()
538 {
539         dprint("vehicle ",self.netname, " used by ", activator.classname, "\n");
540
541         self.tur_head.team = activator.team;
542
543         if(self.tur_head.team == 0)
544                 self.active = ACTIVE_NOT;
545         else
546                 self.active = ACTIVE_ACTIVE;
547
548         if(self.active == ACTIVE_ACTIVE && self.deadflag == DEAD_NO && !gameover)
549         {
550                 dprint("Respawning vehicle: ", self.netname, "\n");
551                 if(self.effects & EF_NODRAW)
552                 {
553                         self.think = vehicles_spawn;
554                         self.nextthink = time + 3;
555                 }
556                 else
557                 {
558                         vehicles_setreturn(self);
559                         vehicles_reset_colors();
560                 }
561         }
562 }
563
564 void vehicles_regen(float timer, .float regen_field, float field_max, float rpause, float regen, float delta_time, float _healthscale)
565 {
566         if(self.regen_field < field_max)
567         if(timer + rpause < time)
568         {
569                 if(_healthscale)
570                         regen = regen * (self.vehicle_health / self.max_health);
571
572                 self.regen_field = min(self.regen_field + regen * delta_time, field_max);
573
574                 if(self.owner)
575                         self.owner.regen_field = (self.regen_field / field_max) * 100;
576         }
577 }
578
579 void shieldhit_think()
580 {
581         self.alpha -= 0.1;
582         if (self.alpha <= 0)
583         {
584                 //setmodel(self, "");
585                 self.alpha = -1;
586                 self.effects |= EF_NODRAW;
587         }
588         else
589         {
590                 self.nextthink = time + 0.1;
591         }
592 }
593
594 void vehicles_painframe()
595 {
596         if(self.owner.vehicle_health <= 50)
597         if(self.pain_frame < time)
598         {
599                 float _ftmp;
600                 _ftmp = self.owner.vehicle_health / 50;
601                 self.pain_frame = time + 0.1 + (random() * 0.5 * _ftmp);
602                 pointparticles(particleeffectnum("smoke_small"), (self.origin + (randomvec() * 80)), '0 0 0', 1);
603
604                 if(self.vehicle_flags & VHF_DMGSHAKE)
605                         self.velocity += randomvec() * 30;
606
607                 if(self.vehicle_flags & VHF_DMGROLL)
608                         if(self.vehicle_flags & VHF_DMGHEADROLL)
609                                 self.tur_head.angles += randomvec();
610                         else
611                                 self.angles += randomvec();
612
613         }
614 }
615
616 void vehicles_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
617 {
618         self.dmg_time = time;
619
620         if(DEATH_ISWEAPON(deathtype, WEP_NEX))
621                 damage *= autocvar_g_vehicles_nex_damagerate;
622
623         if(DEATH_ISWEAPON(deathtype, WEP_UZI))
624                 damage *= autocvar_g_vehicles_uzi_damagerate;
625
626         if(DEATH_ISWEAPON(deathtype, WEP_RIFLE))
627                 damage *= autocvar_g_vehicles_rifle_damagerate;
628
629         if(DEATH_ISWEAPON(deathtype, WEP_MINSTANEX))
630                 damage *= autocvar_g_vehicles_minstanex_damagerate;
631
632         if(DEATH_ISWEAPON(deathtype, WEP_SEEKER))
633                 damage *= autocvar_g_vehicles_tag_damagerate;
634
635         self.enemy = attacker;
636
637         self.pain_finished = time;
638
639         if((self.vehicle_flags & VHF_HASSHIELD) && (self.vehicle_shield > 0))
640         {
641                 if (wasfreed(self.vehicle_shieldent) || self.vehicle_shieldent == world)
642                 {
643                         self.vehicle_shieldent = spawn();
644                         self.vehicle_shieldent.effects = EF_LOWPRECISION;
645
646                         setmodel(self.vehicle_shieldent, "models/vhshield.md3");
647                         setattachment(self.vehicle_shieldent, self, "");
648                         setorigin(self.vehicle_shieldent, real_origin(self) - self.origin);
649                         self.vehicle_shieldent.scale       = 256 / vlen(self.maxs - self.mins);
650                         self.vehicle_shieldent.think       = shieldhit_think;
651                 }
652
653                 self.vehicle_shieldent.colormod = '1 1 1';
654                 self.vehicle_shieldent.alpha = 0.45;
655                 self.vehicle_shieldent.angles = vectoangles(normalize(hitloc - (self.origin + self.vehicle_shieldent.origin))) - self.angles;
656                 self.vehicle_shieldent.nextthink = time;
657                 self.vehicle_shieldent.effects &= ~EF_NODRAW;
658
659                 self.vehicle_shield -= damage;
660
661                 if(self.vehicle_shield < 0)
662                 {
663                         self.vehicle_health -= fabs(self.vehicle_shield);
664                         self.vehicle_shieldent.colormod = '2 0 0';
665                         self.vehicle_shield = 0;
666                         self.vehicle_shieldent.alpha = 0.75;
667
668                         if(sound_allowed(MSG_BROADCAST, attacker))
669                                 spamsound (self, CH_PAIN, "onslaught/ons_hit2.wav", VOL_BASE, ATTEN_NORM);   // FIXME: PLACEHOLDER
670                 }
671                 else
672                         if(sound_allowed(MSG_BROADCAST, attacker))
673                                 spamsound (self, CH_PAIN, "onslaught/electricity_explode.wav", VOL_BASE, ATTEN_NORM);  // FIXME: PLACEHOLDER
674
675         }
676         else
677         {
678                 self.vehicle_health -= damage;
679
680                 if(sound_allowed(MSG_BROADCAST, attacker))
681                         spamsound (self, CH_PAIN, "onslaught/ons_hit2.wav", VOL_BASE, ATTEN_NORM);  // FIXME: PLACEHOLDER
682         }
683
684         if(self.damageforcescale < 1 && self.damageforcescale > 0)
685                 self.velocity += force * self.damageforcescale;
686         else
687                 self.velocity += force;
688
689         if(self.vehicle_health <= 0)
690         {
691                 if(self.owner)
692                         if(self.vehicle_flags & VHF_DEATHEJECT)
693                                 vehicles_exit(VHEF_EJECT);
694                         else
695                                 vehicles_exit(VHEF_RELEASE);
696
697
698                 antilag_clear(self);
699
700                 VEH_ACTION(self.vehicleid, VR_DEATH);
701                 vehicles_setreturn(self);
702         }
703 }
704
705 float vehicles_crushable(entity e)
706 {
707         if(IS_PLAYER(e))
708                 return TRUE;
709
710         if(e.flags & FL_MONSTER)
711                 return TRUE;
712
713         return FALSE;
714 }
715
716 void vehicles_impact(float _minspeed, float _speedfac, float _maxpain)
717 {
718         if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
719                 return;
720
721         if(self.play_time < time)
722         {
723                 float wc = vlen(self.velocity - self.oldvelocity);
724                 //dprint("oldvel: ", vtos(self.oldvelocity), "\n");
725                 //dprint("vel: ", vtos(self.velocity), "\n");
726                 if(_minspeed < wc)
727                 {
728                         float take = min(_speedfac * wc, _maxpain);
729                         Damage (self, world, world, take, DEATH_FALL, self.origin, '0 0 0');
730                         self.play_time = time + 0.25;
731
732                         //dprint("wc: ", ftos(wc), "\n");
733                         //dprint("take: ", ftos(take), "\n");
734                 }
735         }
736 }
737
738 // vehicle enter/exit handling
739 vector vehicles_findgoodexit(vector prefer_spot)
740 {
741         //vector exitspot;
742         float mysize;
743
744         tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, prefer_spot, MOVE_NORMAL, self.owner);
745         if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid)
746                 return prefer_spot;
747
748         mysize = 1.5 * vlen(self.maxs - self.mins);
749         float i;
750         vector v, v2;
751         v2 = 0.5 * (self.absmin + self.absmax);
752         for(i = 0; i < 100; ++i)
753         {
754                 v = randomvec();
755                 v_z = 0;
756                 v = v2 + normalize(v) * mysize;
757                 tracebox(v2, PL_MIN, PL_MAX, v, MOVE_NORMAL, self.owner);
758                 if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid)
759                         return v;
760         }
761
762         /*
763         exitspot = (self.origin + '0 0 48') + v_forward * mysize;
764         tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, exitspot, MOVE_NORMAL, self.owner);
765         if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid)
766                 return exitspot;
767
768         exitspot = (self.origin + '0 0 48') - v_forward * mysize;
769         tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, exitspot, MOVE_NORMAL, self.owner);
770         if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid)
771                 return exitspot;
772
773         exitspot = (self.origin + '0 0 48') + v_right * mysize;
774         tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, exitspot, MOVE_NORMAL, self.owner);
775         if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid)
776                 return exitspot;
777
778         exitspot = (self.origin + '0 0 48') - v_right * mysize;
779         tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, exitspot, MOVE_NORMAL, self.owner);
780         if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid)
781                 return exitspot;
782         */
783
784         return self.origin;
785 }
786
787 void vehicles_exit(float eject)
788 {
789         entity _vehicle;
790         entity _player;
791         entity _oldself = self;
792
793         if(vehicles_exit_running)
794         {
795                 dprint("^1vehicles_exit allready running! this is not good..\n");
796                 return;
797         }
798
799         vehicles_exit_running = TRUE;
800         if(IS_CLIENT(self))
801         {
802                 _vehicle = self.vehicle;
803
804                 if (_vehicle.vehicle_flags & VHF_PLAYERSLOT)
805                 {
806                         _vehicle.vehicle_exit(eject);
807                         self = _oldself;
808                         vehicles_exit_running = FALSE;
809                         return;
810                 }
811         }
812         else
813                 _vehicle = self;
814
815         _player = _vehicle.owner;
816
817         self = _vehicle;
818
819         if (_player)
820         {
821                 if (IS_REAL_CLIENT(_player))
822                 {
823                         msg_entity = _player;
824                         WriteByte (MSG_ONE, SVC_SETVIEWPORT);
825                         WriteEntity( MSG_ONE, _player);
826
827                         WriteByte (MSG_ONE, SVC_SETVIEWANGLES);
828                         WriteAngle(MSG_ONE, 0);
829                         WriteAngle(MSG_ONE, _vehicle.angles_y);
830                         WriteAngle(MSG_ONE, 0);
831                 }
832
833                 setsize(_player, PL_MIN,PL_MAX);
834
835                 _player.takedamage              = DAMAGE_AIM;
836                 _player.solid                   = SOLID_SLIDEBOX;
837                 _player.movetype                = MOVETYPE_WALK;
838                 _player.effects            &= ~EF_NODRAW;
839                 _player.teleportable    = TELEPORT_NORMAL;
840                 _player.alpha                   = 1;
841                 _player.PlayerPhysplug  = func_null;
842                 _player.vehicle                 = world;
843                 _player.view_ofs                = PL_VIEW_OFS;
844                 _player.event_damage    = PlayerDamage;
845                 _player.hud                             = HUD_NORMAL;
846                 _player.switchweapon    = _vehicle.switchweapon;
847                 _player.last_vehiclecheck = time + 3;
848
849                 CSQCVehicleSetup(_player, HUD_NORMAL);
850         }
851         _vehicle.flags |= FL_NOTARGET;
852
853         if(_vehicle.deadflag == DEAD_NO)
854                 _vehicle.avelocity = '0 0 0';
855
856         _vehicle.tur_head.nodrawtoclient = world;
857
858         if(!teamplay)
859                 _vehicle.team = 0;
860
861         Kill_Notification(NOTIF_ONE, _player, MSG_CENTER_CPID, CPID_VEHICLES);
862         Kill_Notification(NOTIF_ONE, _player, MSG_CENTER_CPID, CPID_VEHICLES_OTHER); // kill all vehicle notifications when exiting a vehicle?
863
864         WaypointSprite_Kill(_vehicle.wps_intruder);
865
866         vh_player = _player;
867         vh_vehicle = _vehicle;
868         MUTATOR_CALLHOOK(VehicleExit);
869         _player = vh_player;
870         _vehicle = vh_vehicle;
871
872         _vehicle.team = _vehicle.tur_head.team;
873
874         sound (_vehicle, CH_TRIGGER_SINGLE, "misc/null.wav", 1, ATTEN_NORM);
875         _vehicle.vehicle_hudmodel.viewmodelforclient = _vehicle;
876         _vehicle.phase = time + 1;
877
878         _vehicle.vehicle_exit(eject);
879
880         vehicles_setreturn(_vehicle);
881         vehicles_reset_colors();
882         _vehicle.owner = world;
883
884         CSQCMODEL_AUTOINIT();
885
886         self = _oldself;
887
888         vehicles_exit_running = FALSE;
889 }
890
891 void vehicles_touch()
892 {
893         if(MUTATOR_CALLHOOK(VehicleTouch))
894                 return;
895
896         // Vehicle currently in use
897         if(self.owner)
898         {
899                 if(!forbidWeaponUse(self.owner))
900                 if(other != world)
901                 if((self.origin_z + self.maxs_z) > (other.origin_z))
902                 if(vehicles_crushable(other))
903                 {
904                         if(vlen(self.velocity) != 0)
905                                 Damage(other, self, self.owner, autocvar_g_vehicles_crush_dmg, DEATH_VH_CRUSH, '0 0 0', normalize(other.origin - self.origin) * autocvar_g_vehicles_crush_force);
906
907                         return; // Dont do selfdamage when hitting "soft targets".
908                 }
909
910                 if(self.play_time < time)
911                         VEH_ACTION(self.vehicleid, VR_IMPACT);
912
913                 return;
914         }
915
916         if(autocvar_g_vehicles_enter)
917                 return;
918
919         vehicles_enter(other, self);
920 }
921
922 void vehicles_enter(entity pl, entity veh)
923 {
924    // Remove this when bots know how to use vehicles
925         if (IS_BOT_CLIENT(pl))
926         if (autocvar_g_vehicles_allow_bots)
927                 dprint("Bot enters vehicle\n"); // This is where we need to disconnect (some, all?) normal bot AI and hand over to vehicle's _aiframe()
928         else
929                 return;
930
931         if(!IS_PLAYER(pl))
932                 return;
933
934         if(veh.phase > time)
935                 return;
936
937         if(pl.frozen)
938                 return;
939
940         if(pl.deadflag != DEAD_NO)
941                 return;
942
943         if(pl.vehicle)
944                 return;
945
946         if(autocvar_g_vehicles_enter) // skip if we're using regular touch code
947         if(veh.vehicle_flags & VHF_MULTISLOT)
948         if(veh.owner)
949         {
950                 entity oldself = self;
951                 self = veh;
952                 other = pl; // TODO: fix
953
954                 if(!veh.gunner1)
955                 if(veh.gun1.phase <= time)
956                 if(veh.gun1.vehicle_enter)
957                 if(veh.gun1.vehicle_enter())
958                 {
959                         self = oldself;
960                         return;
961                 }
962
963                 if(!veh.gunner2)
964                 if(veh.gun2.phase <= time)
965                 if(veh.gun2.vehicle_enter)
966                 if(veh.gun2.vehicle_enter())
967                 {
968                         self = oldself;
969                         return;
970                 }
971
972                 self = oldself;
973         }
974
975         if(teamplay)
976         if(veh.team)
977         if(DIFF_TEAM(pl, veh))
978         if(autocvar_g_vehicles_steal)
979         {
980                 entity head;
981                 FOR_EACH_PLAYER(head) if(SAME_TEAM(head, veh))
982                         Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_VEHICLE_STEAL);
983
984                 Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_VEHICLE_STEAL_SELF);
985
986                 if(autocvar_g_vehicles_steal_show_waypoint)
987                         WaypointSprite_Spawn("intruder", 0, 0, pl, '0 0 68', world, veh.team, veh, wps_intruder, TRUE, RADARICON_DANGER, Team_ColorRGB(pl.team));
988         }
989         else return;
990
991         RemoveGrapplingHook(pl);
992
993         veh.vehicle_ammo1 = 0;
994         veh.vehicle_ammo2 = 0;
995         veh.vehicle_reload1 = 0;
996         veh.vehicle_reload2 = 0;
997         veh.vehicle_energy = 0;
998
999         veh.owner = pl;
1000         pl.vehicle = veh;
1001
1002         // .viewmodelforclient works better.
1003         //veh.vehicle_hudmodel.drawonlytoclient = veh.owner;
1004
1005         veh.vehicle_hudmodel.viewmodelforclient = pl;
1006
1007         tracebox(pl.origin, PL_MIN, PL_MAX, pl.origin, FALSE, pl);
1008         pl.crouch = FALSE;
1009         pl.view_ofs = PL_VIEW_OFS;
1010         setsize (pl, PL_MIN, PL_MAX);
1011
1012         veh.event_damage        = vehicles_damage;
1013         veh.nextthink           = 0;
1014         pl.angles                       = veh.angles;
1015         pl.takedamage           = DAMAGE_NO;
1016         pl.solid                        = SOLID_NOT;
1017         pl.movetype                     = MOVETYPE_NOCLIP;
1018         pl.teleportable         = FALSE;
1019         pl.alpha                        = -1;
1020         pl.event_damage         = func_null;
1021         pl.view_ofs                     = '0 0 0';
1022         veh.colormap            = pl.colormap;
1023         if(veh.tur_head)
1024                 veh.tur_head.colormap = pl.colormap;
1025         veh.switchweapon = pl.switchweapon;
1026         pl.hud = veh.vehicleid;
1027         pl.PlayerPhysplug = veh.PlayerPhysplug;
1028
1029         pl.vehicle_ammo1 = veh.vehicle_ammo1;
1030         pl.vehicle_ammo2 = veh.vehicle_ammo2;
1031         pl.vehicle_reload1 = veh.vehicle_reload1;
1032         pl.vehicle_reload2 = veh.vehicle_reload2;
1033
1034         // Cant do this, hides attached objects too.
1035         //veh.exteriormodeltoclient = veh.owner;
1036         //veh.tur_head.exteriormodeltoclient = veh.owner;
1037
1038         pl.flags &= ~FL_ONGROUND;
1039         veh.flags &= ~FL_ONGROUND;
1040
1041         veh.team = pl.team;
1042         veh.flags -= FL_NOTARGET;
1043
1044         if (IS_REAL_CLIENT(pl))
1045         {
1046                 Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_VEHICLE_ENTER);
1047
1048                 msg_entity = pl;
1049                 WriteByte (MSG_ONE, SVC_SETVIEWPORT);
1050                 WriteEntity(MSG_ONE, veh.vehicle_viewport);
1051
1052                 WriteByte (MSG_ONE, SVC_SETVIEWANGLES);
1053                 if(veh.tur_head)
1054                 {
1055                         WriteAngle(MSG_ONE, veh.tur_head.angles_x + veh.angles_x); // tilt
1056                         WriteAngle(MSG_ONE, veh.tur_head.angles_y + veh.angles_y); // yaw
1057                         WriteAngle(MSG_ONE, 0);                                                                   // roll
1058                 }
1059                 else
1060                 {
1061                         WriteAngle(MSG_ONE, veh.angles_x * -1); // tilt
1062                         WriteAngle(MSG_ONE, veh.angles_y);        // yaw
1063                         WriteAngle(MSG_ONE, 0);                           // roll
1064                 }
1065         }
1066
1067         vehicles_clearreturn(veh);
1068
1069         CSQCVehicleSetup(pl, veh.vehicleid);
1070
1071         vh_player = pl;
1072         vh_vehicle = veh;
1073         MUTATOR_CALLHOOK(VehicleEnter);
1074
1075         entity oldself = self;
1076         self = veh;
1077         CSQCModel_UnlinkEntity();
1078         VEH_ACTION(veh.vehicleid, VR_ENTER);
1079         self = oldself;
1080
1081         antilag_clear(pl);
1082 }
1083
1084 void vehicles_think()
1085 {
1086         self.nextthink = time;
1087         
1088         if(self.owner)
1089                 self.owner.vehicle_weapon2mode = self.vehicle_weapon2mode;
1090         
1091         VEH_ACTION(self.vehicleid, VR_THINK);
1092
1093         CSQCMODEL_AUTOUPDATE();
1094 }
1095
1096 // initialization
1097 void vehicles_spawn()
1098 {
1099         dprint("Spawning vehicle: ", self.classname, "\n");
1100
1101         // disown & reset
1102         self.vehicle_hudmodel.viewmodelforclient = self;
1103
1104         self.owner                              = world;
1105         self.touch                              = vehicles_touch;
1106         self.event_damage               = vehicles_damage;
1107         self.iscreature                 = TRUE;
1108         self.teleportable               = FALSE; // no teleporting for vehicles, too buggy
1109         self.damagedbycontents  = TRUE;
1110         self.movetype                   = MOVETYPE_WALK;
1111         self.solid                              = SOLID_SLIDEBOX;
1112         self.takedamage                 = DAMAGE_AIM;
1113         self.deadflag                   = DEAD_NO;
1114         self.bot_attack                 = TRUE;
1115         self.flags                              = FL_NOTARGET;
1116         self.avelocity                  = '0 0 0';
1117         self.velocity                   = '0 0 0';
1118         self.think                              = vehicles_think;
1119         self.nextthink                  = time;
1120
1121         // Reset locking
1122         self.lock_strength = 0;
1123         self.lock_target = world;
1124         self.misc_bulletcounter = 0;
1125
1126         // Return to spawn
1127         self.angles = self.pos2;
1128         setorigin(self, self.pos1);
1129         // Show it
1130         pointparticles(particleeffectnum("teleport"), self.origin + '0 0 64', '0 0 0', 1);
1131
1132         if(self.vehicle_controller)
1133                 self.team = self.vehicle_controller.team;
1134
1135         entity head; // remove hooks (if any)
1136         FOR_EACH_PLAYER(head)
1137         if(head.hook.aiment == self)
1138                 RemoveGrapplingHook(head);
1139
1140         vehicles_reset_colors();
1141
1142         VEH_ACTION(self.vehicleid, VR_SPAWN);
1143
1144         CSQCMODEL_AUTOINIT();
1145 }
1146
1147 float vehicle_initialize(float vehicle_id, float nodrop)
1148 {
1149         if(!autocvar_g_vehicles)
1150                 return FALSE;
1151
1152         entity veh = get_vehicleinfo(vehicle_id);
1153
1154         if(!veh.vehicleid)
1155                 return FALSE;
1156         
1157         if(!veh.tur_head) { VEH_ACTION(vehicle_id, VR_PRECACHE); }
1158
1159         if(self.targetname && self.targetname != "")
1160         {
1161                 self.vehicle_controller = find(world, target, self.targetname);
1162                 if(!self.vehicle_controller)
1163                 {
1164                         bprint("^1WARNING: ^7Vehicle with invalid .targetname\n");
1165                         self.active = ACTIVE_ACTIVE;
1166                 }
1167                 else
1168                 {
1169                         self.team = self.vehicle_controller.team;
1170                         self.use = vehicle_use;
1171
1172                         if(teamplay)
1173                         {
1174                                 if(self.vehicle_controller.team == 0)
1175                                         self.active = ACTIVE_NOT;
1176                                 else
1177                                         self.active = ACTIVE_ACTIVE;
1178                         }
1179                 }
1180         }
1181         else { self.active = ACTIVE_ACTIVE; }
1182
1183         if(self.team && (!teamplay || !autocvar_g_vehicles_teams))
1184                 self.team = 0;
1185
1186         self.vehicle_flags |= VHF_ISVEHICLE;
1187
1188         setmodel(self, veh.model);
1189
1190         self.vehicle_viewport           = spawn();
1191         self.vehicle_hudmodel           = spawn();
1192         self.tur_head                           = spawn();
1193         self.tur_head.owner                     = self;
1194         self.takedamage                         = DAMAGE_NO;
1195         self.bot_attack                         = TRUE;
1196         self.iscreature                         = TRUE;
1197         self.teleportable                       = FALSE; // no teleporting for vehicles, too buggy
1198         self.damagedbycontents          = TRUE;
1199         self.vehicleid                          = vehicle_id;
1200         self.PlayerPhysplug                     = veh.PlayerPhysplug;
1201         self.event_damage                       = func_null;
1202         self.touch                                      = vehicles_touch;
1203         self.think                                      = vehicles_spawn;
1204         self.nextthink                          = time;
1205         self.effects                            = EF_NODRAW;
1206         self.dphitcontentsmask          = DPCONTENTS_BODY | DPCONTENTS_SOLID;
1207
1208         if(autocvar_g_playerclip_collisions)
1209                 self.dphitcontentsmask |= DPCONTENTS_PLAYERCLIP;
1210
1211         if(autocvar_g_nodepthtestplayers)
1212                 self.effects |= EF_NODEPTHTEST;
1213
1214         if(autocvar_g_fullbrightplayers)
1215                 self.effects |= EF_FULLBRIGHT;
1216
1217         setmodel(self.vehicle_hudmodel, veh.hud_model);
1218         setmodel(self.vehicle_viewport, "null");
1219
1220         if(veh.head_model != "")
1221         {
1222                 setmodel(self.tur_head, veh.head_model);
1223                 setattachment(self.tur_head, self, veh.tag_head);
1224                 setattachment(self.vehicle_hudmodel, self.tur_head, veh.tag_hud);
1225                 setattachment(self.vehicle_viewport, self.vehicle_hudmodel, veh.tag_view);
1226         }
1227         else
1228         {
1229                 setattachment(self.tur_head, self, "");
1230                 setattachment(self.vehicle_hudmodel, self, veh.tag_hud);
1231                 setattachment(self.vehicle_viewport, self.vehicle_hudmodel, veh.tag_view);
1232         }
1233
1234         setsize(self, veh.mins, veh.maxs);
1235
1236         if(!nodrop)
1237         {
1238                 setorigin(self, self.origin);
1239                 tracebox(self.origin + '0 0 100', veh.mins, veh.maxs, self.origin - '0 0 10000', MOVE_WORLDONLY, self);
1240                 setorigin(self, trace_endpos);
1241         }
1242
1243         self.pos1 = self.origin;
1244         self.pos2 = self.angles;
1245         self.tur_head.team = self.team;
1246
1247         VEH_ACTION(vehicle_id, VR_SETUP);
1248
1249         if(self.active == ACTIVE_NOT)
1250                 self.nextthink = 0; // wait until activated
1251         else if(autocvar_g_vehicles_delayspawn)
1252                 self.nextthink = time + self.respawntime + (random() * autocvar_g_vehicles_delayspawn_jitter);
1253         else
1254                 self.nextthink = time + game_starttime;
1255
1256         if(MUTATOR_CALLHOOK(VehicleSpawn))
1257                 return FALSE;
1258
1259         return TRUE;
1260 }