]> 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)
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                 vehicles_setreturn(self);
552                 vehicles_reset_colors();
553         }
554 }
555
556 void vehicles_regen(float timer, .float regen_field, float field_max, float rpause, float regen, float delta_time, float _healthscale)
557 {
558         if(self.regen_field < field_max)
559         if(timer + rpause < time)
560         {
561                 if(_healthscale)
562                         regen = regen * (self.vehicle_health / self.max_health);
563
564                 self.regen_field = min(self.regen_field + regen * delta_time, field_max);
565
566                 if(self.owner)
567                         self.owner.regen_field = (self.regen_field / field_max) * 100;
568         }
569 }
570
571 void shieldhit_think()
572 {
573         self.alpha -= 0.1;
574         if (self.alpha <= 0)
575         {
576                 //setmodel(self, "");
577                 self.alpha = -1;
578                 self.effects |= EF_NODRAW;
579         }
580         else
581         {
582                 self.nextthink = time + 0.1;
583         }
584 }
585
586 void vehicles_painframe()
587 {
588         if(self.owner.vehicle_health <= 50)
589         if(self.pain_frame < time)
590         {
591                 float _ftmp;
592                 _ftmp = self.owner.vehicle_health / 50;
593                 self.pain_frame = time + 0.1 + (random() * 0.5 * _ftmp);
594                 pointparticles(particleeffectnum("smoke_small"), (self.origin + (randomvec() * 80)), '0 0 0', 1);
595
596                 if(self.vehicle_flags & VHF_DMGSHAKE)
597                         self.velocity += randomvec() * 30;
598
599                 if(self.vehicle_flags & VHF_DMGROLL)
600                         if(self.vehicle_flags & VHF_DMGHEADROLL)
601                                 self.tur_head.angles += randomvec();
602                         else
603                                 self.angles += randomvec();
604
605         }
606 }
607
608 void vehicles_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
609 {
610         self.dmg_time = time;
611
612         if(DEATH_ISWEAPON(deathtype, WEP_NEX))
613                 damage *= autocvar_g_vehicles_nex_damagerate;
614
615         if(DEATH_ISWEAPON(deathtype, WEP_UZI))
616                 damage *= autocvar_g_vehicles_uzi_damagerate;
617
618         if(DEATH_ISWEAPON(deathtype, WEP_RIFLE))
619                 damage *= autocvar_g_vehicles_rifle_damagerate;
620
621         if(DEATH_ISWEAPON(deathtype, WEP_MINSTANEX))
622                 damage *= autocvar_g_vehicles_minstanex_damagerate;
623
624         if(DEATH_ISWEAPON(deathtype, WEP_SEEKER))
625                 damage *= autocvar_g_vehicles_tag_damagerate;
626
627         self.enemy = attacker;
628
629         self.pain_finished = time;
630
631         if((self.vehicle_flags & VHF_HASSHIELD) && (self.vehicle_shield > 0))
632         {
633                 if (wasfreed(self.vehicle_shieldent) || self.vehicle_shieldent == world)
634                 {
635                         self.vehicle_shieldent = spawn();
636                         self.vehicle_shieldent.effects = EF_LOWPRECISION;
637
638                         setmodel(self.vehicle_shieldent, "models/vhshield.md3");
639                         setattachment(self.vehicle_shieldent, self, "");
640                         setorigin(self.vehicle_shieldent, real_origin(self) - self.origin);
641                         self.vehicle_shieldent.scale       = 256 / vlen(self.maxs - self.mins);
642                         self.vehicle_shieldent.think       = shieldhit_think;
643                 }
644
645                 self.vehicle_shieldent.colormod = '1 1 1';
646                 self.vehicle_shieldent.alpha = 0.45;
647                 self.vehicle_shieldent.angles = vectoangles(normalize(hitloc - (self.origin + self.vehicle_shieldent.origin))) - self.angles;
648                 self.vehicle_shieldent.nextthink = time;
649                 self.vehicle_shieldent.effects &= ~EF_NODRAW;
650
651                 self.vehicle_shield -= damage;
652
653                 if(self.vehicle_shield < 0)
654                 {
655                         self.vehicle_health -= fabs(self.vehicle_shield);
656                         self.vehicle_shieldent.colormod = '2 0 0';
657                         self.vehicle_shield = 0;
658                         self.vehicle_shieldent.alpha = 0.75;
659
660                         if(sound_allowed(MSG_BROADCAST, attacker))
661                                 spamsound (self, CH_PAIN, "onslaught/ons_hit2.wav", VOL_BASE, ATTEN_NORM);   // FIXME: PLACEHOLDER
662                 }
663                 else
664                         if(sound_allowed(MSG_BROADCAST, attacker))
665                                 spamsound (self, CH_PAIN, "onslaught/electricity_explode.wav", VOL_BASE, ATTEN_NORM);  // FIXME: PLACEHOLDER
666
667         }
668         else
669         {
670                 self.vehicle_health -= damage;
671
672                 if(sound_allowed(MSG_BROADCAST, attacker))
673                         spamsound (self, CH_PAIN, "onslaught/ons_hit2.wav", VOL_BASE, ATTEN_NORM);  // FIXME: PLACEHOLDER
674         }
675
676         if(self.damageforcescale < 1 && self.damageforcescale > 0)
677                 self.velocity += force * self.damageforcescale;
678         else
679                 self.velocity += force;
680
681         if(self.vehicle_health <= 0)
682         {
683                 if(self.owner)
684                         if(self.vehicle_flags & VHF_DEATHEJECT)
685                                 vehicles_exit(VHEF_EJECT);
686                         else
687                                 vehicles_exit(VHEF_RELEASE);
688
689
690                 antilag_clear(self);
691
692                 VEH_ACTION(self.vehicleid, VR_DEATH);
693                 vehicles_setreturn(self);
694         }
695 }
696
697 float vehicles_crushable(entity e)
698 {
699         if(IS_PLAYER(e))
700                 return TRUE;
701
702         if(e.flags & FL_MONSTER)
703                 return TRUE;
704
705         return FALSE;
706 }
707
708 void vehicles_impact(float _minspeed, float _speedfac, float _maxpain)
709 {
710         if (trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
711                 return;
712
713         if(self.play_time < time)
714         {
715                 float wc = vlen(self.velocity - self.oldvelocity);
716                 //dprint("oldvel: ", vtos(self.oldvelocity), "\n");
717                 //dprint("vel: ", vtos(self.velocity), "\n");
718                 if(_minspeed < wc)
719                 {
720                         float take = min(_speedfac * wc, _maxpain);
721                         Damage (self, world, world, take, DEATH_FALL, self.origin, '0 0 0');
722                         self.play_time = time + 0.25;
723
724                         //dprint("wc: ", ftos(wc), "\n");
725                         //dprint("take: ", ftos(take), "\n");
726                 }
727         }
728 }
729
730 // vehicle enter/exit handling
731 vector vehicles_findgoodexit(vector prefer_spot)
732 {
733         //vector exitspot;
734         float mysize;
735
736         tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, prefer_spot, MOVE_NORMAL, self.owner);
737         if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid)
738                 return prefer_spot;
739
740         mysize = 1.5 * vlen(self.maxs - self.mins);
741         float i;
742         vector v, v2;
743         v2 = 0.5 * (self.absmin + self.absmax);
744         for(i = 0; i < 100; ++i)
745         {
746                 v = randomvec();
747                 v_z = 0;
748                 v = v2 + normalize(v) * mysize;
749                 tracebox(v2, PL_MIN, PL_MAX, v, MOVE_NORMAL, self.owner);
750                 if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid)
751                         return v;
752         }
753
754         /*
755         exitspot = (self.origin + '0 0 48') + v_forward * mysize;
756         tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, exitspot, MOVE_NORMAL, self.owner);
757         if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid)
758                 return exitspot;
759
760         exitspot = (self.origin + '0 0 48') - v_forward * mysize;
761         tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, exitspot, MOVE_NORMAL, self.owner);
762         if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid)
763                 return exitspot;
764
765         exitspot = (self.origin + '0 0 48') + v_right * mysize;
766         tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, exitspot, MOVE_NORMAL, self.owner);
767         if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid)
768                 return exitspot;
769
770         exitspot = (self.origin + '0 0 48') - v_right * mysize;
771         tracebox(self.origin + '0 0 32', PL_MIN, PL_MAX, exitspot, MOVE_NORMAL, self.owner);
772         if(trace_fraction == 1.0 && !trace_startsolid && !trace_allsolid)
773                 return exitspot;
774         */
775
776         return self.origin;
777 }
778
779 void vehicles_exit(float eject)
780 {
781         entity _vehicle;
782         entity _player;
783         entity _oldself = self;
784
785         if(vehicles_exit_running)
786         {
787                 dprint("^1vehicles_exit allready running! this is not good..\n");
788                 return;
789         }
790
791         vehicles_exit_running = TRUE;
792         if(IS_CLIENT(self))
793         {
794                 _vehicle = self.vehicle;
795
796                 if (_vehicle.vehicle_flags & VHF_PLAYERSLOT)
797                 {
798                         _vehicle.vehicle_exit(eject);
799                         self = _oldself;
800                         vehicles_exit_running = FALSE;
801                         return;
802                 }
803         }
804         else
805                 _vehicle = self;
806
807         _player = _vehicle.owner;
808
809         self = _vehicle;
810
811         if (_player)
812         {
813                 if (IS_REAL_CLIENT(_player))
814                 {
815                         msg_entity = _player;
816                         WriteByte (MSG_ONE, SVC_SETVIEWPORT);
817                         WriteEntity( MSG_ONE, _player);
818
819                         WriteByte (MSG_ONE, SVC_SETVIEWANGLES);
820                         WriteAngle(MSG_ONE, 0);
821                         WriteAngle(MSG_ONE, _vehicle.angles_y);
822                         WriteAngle(MSG_ONE, 0);
823                 }
824
825                 setsize(_player, PL_MIN,PL_MAX);
826
827                 _player.takedamage              = DAMAGE_AIM;
828                 _player.solid                   = SOLID_SLIDEBOX;
829                 _player.movetype                = MOVETYPE_WALK;
830                 _player.effects            &= ~EF_NODRAW;
831                 _player.teleportable    = TELEPORT_NORMAL;
832                 _player.alpha                   = 1;
833                 _player.PlayerPhysplug  = func_null;
834                 _player.vehicle                 = world;
835                 _player.view_ofs                = PL_VIEW_OFS;
836                 _player.event_damage    = PlayerDamage;
837                 _player.hud                             = HUD_NORMAL;
838                 _player.switchweapon    = _vehicle.switchweapon;
839                 _player.last_vehiclecheck = time + 3;
840
841                 CSQCVehicleSetup(_player, HUD_NORMAL);
842         }
843         _vehicle.flags |= FL_NOTARGET;
844
845         if(_vehicle.deadflag == DEAD_NO)
846                 _vehicle.avelocity = '0 0 0';
847
848         _vehicle.tur_head.nodrawtoclient = world;
849
850         if(!teamplay)
851                 _vehicle.team = 0;
852
853         Kill_Notification(NOTIF_ONE, _player, MSG_CENTER_CPID, CPID_VEHICLES);
854         Kill_Notification(NOTIF_ONE, _player, MSG_CENTER_CPID, CPID_VEHICLES_OTHER); // kill all vehicle notifications when exiting a vehicle?
855
856         WaypointSprite_Kill(_vehicle.wps_intruder);
857
858         vh_player = _player;
859         vh_vehicle = _vehicle;
860         MUTATOR_CALLHOOK(VehicleExit);
861         _player = vh_player;
862         _vehicle = vh_vehicle;
863
864         _vehicle.team = _vehicle.tur_head.team;
865
866         sound (_vehicle, CH_TRIGGER_SINGLE, "misc/null.wav", 1, ATTEN_NORM);
867         _vehicle.vehicle_hudmodel.viewmodelforclient = _vehicle;
868         _vehicle.phase = time + 1;
869
870         _vehicle.vehicle_exit(eject);
871
872         vehicles_setreturn(_vehicle);
873         vehicles_reset_colors();
874         _vehicle.owner = world;
875
876         CSQCMODEL_AUTOINIT();
877
878         self = _oldself;
879
880         vehicles_exit_running = FALSE;
881 }
882
883 void vehicles_touch()
884 {
885         if(MUTATOR_CALLHOOK(VehicleTouch))
886                 return;
887
888         // Vehicle currently in use
889         if(self.owner)
890         {
891                 if(!forbidWeaponUse(self.owner))
892                 if(other != world)
893                 if((self.origin_z + self.maxs_z) > (other.origin_z))
894                 if(vehicles_crushable(other))
895                 {
896                         if(vlen(self.velocity) != 0)
897                                 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);
898
899                         return; // Dont do selfdamage when hitting "soft targets".
900                 }
901
902                 if(self.play_time < time)
903                         VEH_ACTION(self.vehicleid, VR_IMPACT);
904
905                 return;
906         }
907
908         if(autocvar_g_vehicles_enter)
909                 return;
910
911         vehicles_enter(other, self);
912 }
913
914 void vehicles_enter(entity pl, entity veh)
915 {
916    // Remove this when bots know how to use vehicles
917         if (IS_BOT_CLIENT(pl))
918         if (autocvar_g_vehicles_allow_bots)
919                 dprint("Bot enters vehicle\n"); // This is where we need to disconnect (some, all?) normal bot AI and hand over to vehicle's _aiframe()
920         else
921                 return;
922
923         if(!IS_PLAYER(pl))
924                 return;
925
926         if(veh.phase > time)
927                 return;
928
929         if(pl.freezetag_frozen)
930                 return;
931
932         if(pl.deadflag != DEAD_NO)
933                 return;
934
935         if(pl.vehicle)
936                 return;
937
938         if(autocvar_g_vehicles_enter) // skip if we're using regular touch code
939         if(veh.vehicle_flags & VHF_MULTISLOT)
940         if(veh.owner)
941         {
942                 entity oldself = self;
943                 self = veh;
944                 other = pl; // TODO: fix
945
946                 if(!veh.gunner1)
947                 if(veh.gun1.phase <= time)
948                 if(veh.gun1.vehicle_enter)
949                 if(veh.gun1.vehicle_enter())
950                 {
951                         self = oldself;
952                         return;
953                 }
954
955                 if(!veh.gunner2)
956                 if(veh.gun2.phase <= time)
957                 if(veh.gun2.vehicle_enter)
958                 if(veh.gun2.vehicle_enter())
959                 {
960                         self = oldself;
961                         return;
962                 }
963
964                 self = oldself;
965         }
966
967         if(teamplay)
968         if(veh.team)
969         if(DIFF_TEAM(pl, veh))
970         if(autocvar_g_vehicles_steal)
971         {
972                 entity head;
973                 FOR_EACH_PLAYER(head) if(SAME_TEAM(head, veh))
974                         Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_VEHICLE_STEAL);
975
976                 Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_VEHICLE_STEAL_SELF);
977
978                 if(autocvar_g_vehicles_steal_show_waypoint)
979                         WaypointSprite_Spawn("intruder", 0, 0, pl, '0 0 68', world, veh.team, veh, wps_intruder, TRUE, RADARICON_DANGER, Team_ColorRGB(pl.team));
980         }
981         else return;
982
983         RemoveGrapplingHook(pl);
984
985         veh.vehicle_ammo1 = 0;
986         veh.vehicle_ammo2 = 0;
987         veh.vehicle_reload1 = 0;
988         veh.vehicle_reload2 = 0;
989         veh.vehicle_energy = 0;
990
991         veh.owner = pl;
992         pl.vehicle = veh;
993
994         // .viewmodelforclient works better.
995         //veh.vehicle_hudmodel.drawonlytoclient = veh.owner;
996
997         veh.vehicle_hudmodel.viewmodelforclient = pl;
998
999         pl.crouch = FALSE;
1000         pl.view_ofs = PL_VIEW_OFS;
1001         setsize (pl, PL_MIN, PL_MAX);
1002
1003         veh.event_damage        = vehicles_damage;
1004         veh.nextthink           = 0;
1005         pl.angles                       = veh.angles;
1006         pl.takedamage           = DAMAGE_NO;
1007         pl.solid                        = SOLID_NOT;
1008         pl.movetype                     = MOVETYPE_NOCLIP;
1009         pl.teleportable         = FALSE;
1010         pl.alpha                        = -1;
1011         pl.event_damage         = func_null;
1012         pl.view_ofs                     = '0 0 0';
1013         veh.colormap            = pl.colormap;
1014         if(veh.tur_head)
1015                 veh.tur_head.colormap = pl.colormap;
1016         veh.switchweapon = pl.switchweapon;
1017         pl.hud = veh.vehicleid;
1018         pl.PlayerPhysplug = veh.PlayerPhysplug;
1019
1020         pl.vehicle_ammo1 = veh.vehicle_ammo1;
1021         pl.vehicle_ammo2 = veh.vehicle_ammo2;
1022         pl.vehicle_reload1 = veh.vehicle_reload1;
1023         pl.vehicle_reload2 = veh.vehicle_reload2;
1024
1025         // Cant do this, hides attached objects too.
1026         //veh.exteriormodeltoclient = veh.owner;
1027         //veh.tur_head.exteriormodeltoclient = veh.owner;
1028
1029         pl.flags &= ~FL_ONGROUND;
1030         veh.flags &= ~FL_ONGROUND;
1031
1032         veh.team = pl.team;
1033         veh.flags -= FL_NOTARGET;
1034
1035         if (IS_REAL_CLIENT(pl))
1036         {
1037                 Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_VEHICLE_ENTER);
1038
1039                 msg_entity = pl;
1040                 WriteByte (MSG_ONE, SVC_SETVIEWPORT);
1041                 WriteEntity(MSG_ONE, veh.vehicle_viewport);
1042
1043                 WriteByte (MSG_ONE, SVC_SETVIEWANGLES);
1044                 if(veh.tur_head)
1045                 {
1046                         WriteAngle(MSG_ONE, veh.tur_head.angles_x + veh.angles_x); // tilt
1047                         WriteAngle(MSG_ONE, veh.tur_head.angles_y + veh.angles_y); // yaw
1048                         WriteAngle(MSG_ONE, 0);                                                                   // roll
1049                 }
1050                 else
1051                 {
1052                         WriteAngle(MSG_ONE, veh.angles_x * -1); // tilt
1053                         WriteAngle(MSG_ONE, veh.angles_y);        // yaw
1054                         WriteAngle(MSG_ONE, 0);                           // roll
1055                 }
1056         }
1057
1058         vehicles_clearreturn(veh);
1059
1060         CSQCVehicleSetup(pl, veh.vehicleid);
1061
1062         vh_player = pl;
1063         vh_vehicle = veh;
1064         MUTATOR_CALLHOOK(VehicleEnter);
1065
1066         entity oldself = self;
1067         self = veh;
1068         CSQCModel_UnlinkEntity();
1069         VEH_ACTION(veh.vehicleid, VR_ENTER);
1070         self = oldself;
1071
1072         antilag_clear(pl);
1073 }
1074
1075 void vehicles_think()
1076 {
1077         self.nextthink = time;
1078         
1079         if(self.owner)
1080                 self.owner.vehicle_weapon2mode = self.vehicle_weapon2mode;
1081         
1082         VEH_ACTION(self.vehicleid, VR_THINK);
1083
1084         CSQCMODEL_AUTOUPDATE();
1085 }
1086
1087 // initialization
1088 void vehicles_spawn()
1089 {
1090         dprint("Spawning vehicle: ", self.classname, "\n");
1091
1092         // disown & reset
1093         self.vehicle_hudmodel.viewmodelforclient = self;
1094
1095         self.owner                              = world;
1096         self.touch                              = vehicles_touch;
1097         self.event_damage               = vehicles_damage;
1098         self.iscreature                 = TRUE;
1099         self.teleportable               = FALSE; // no teleporting for vehicles, too buggy
1100         self.damagedbycontents  = TRUE;
1101         self.movetype                   = MOVETYPE_WALK;
1102         self.solid                              = SOLID_SLIDEBOX;
1103         self.takedamage                 = DAMAGE_AIM;
1104         self.deadflag                   = DEAD_NO;
1105         self.bot_attack                 = TRUE;
1106         self.flags                              = FL_NOTARGET;
1107         self.avelocity                  = '0 0 0';
1108         self.velocity                   = '0 0 0';
1109         self.think                              = vehicles_think;
1110         self.nextthink                  = time;
1111
1112         // Reset locking
1113         self.lock_strength = 0;
1114         self.lock_target = world;
1115         self.misc_bulletcounter = 0;
1116
1117         // Return to spawn
1118         self.angles = self.pos2;
1119         setorigin(self, self.pos1);
1120         // Show it
1121         pointparticles(particleeffectnum("teleport"), self.origin + '0 0 64', '0 0 0', 1);
1122
1123         if(self.vehicle_controller)
1124                 self.team = self.vehicle_controller.team;
1125
1126         entity head; // remove hooks (if any)
1127         FOR_EACH_PLAYER(head)
1128         if(head.hook.aiment == self)
1129                 RemoveGrapplingHook(head);
1130
1131         vehicles_reset_colors();
1132
1133         VEH_ACTION(self.vehicleid, VR_SPAWN);
1134
1135         CSQCMODEL_AUTOINIT();
1136 }
1137
1138 float vehicle_initialize(float vehicle_id, float nodrop)
1139 {
1140         if(!autocvar_g_vehicles)
1141                 return FALSE;
1142
1143         entity veh = get_vehicleinfo(vehicle_id);
1144
1145         if(!veh.vehicleid)
1146                 return FALSE;
1147
1148         if(self.targetname)
1149         {
1150                 self.vehicle_controller = find(world, target, self.targetname);
1151                 if(!self.vehicle_controller)
1152                 {
1153                         bprint("^1WARNING: ^7Vehicle with invalid .targetname\n");
1154                 }
1155                 else
1156                 {
1157                         self.team = self.vehicle_controller.team;
1158                         self.use = vehicle_use;
1159
1160                         if(teamplay)
1161                         {
1162                                 if(self.vehicle_controller.team == 0)
1163                                         self.active = ACTIVE_NOT;
1164                                 else
1165                                         self.active = ACTIVE_ACTIVE;
1166                         }
1167                 }
1168         }
1169
1170         if(self.team && (!teamplay || !autocvar_g_vehicles_teams))
1171                 self.team = 0;
1172
1173         self.vehicle_flags |= VHF_ISVEHICLE;
1174
1175         setmodel(self, veh.model);
1176
1177         self.vehicle_viewport           = spawn();
1178         self.vehicle_hudmodel           = spawn();
1179         self.tur_head                           = spawn();
1180         self.tur_head.owner                     = self;
1181         self.takedamage                         = DAMAGE_NO;
1182         self.bot_attack                         = TRUE;
1183         self.iscreature                         = TRUE;
1184         self.teleportable                       = FALSE; // no teleporting for vehicles, too buggy
1185         self.damagedbycontents          = TRUE;
1186         self.vehicleid                          = vehicle_id;
1187         self.PlayerPhysplug                     = veh.PlayerPhysplug;
1188         self.event_damage                       = func_null;
1189         self.touch                                      = vehicles_touch;
1190         self.think                                      = vehicles_spawn;
1191         self.nextthink                          = time;
1192         self.effects                            = EF_NODRAW;
1193         self.dphitcontentsmask          = DPCONTENTS_BODY | DPCONTENTS_SOLID;
1194
1195         if(autocvar_g_playerclip_collisions)
1196                 self.dphitcontentsmask |= DPCONTENTS_PLAYERCLIP;
1197
1198         if(autocvar_g_nodepthtestplayers)
1199                 self.effects |= EF_NODEPTHTEST;
1200
1201         if(autocvar_g_fullbrightplayers)
1202                 self.effects |= EF_FULLBRIGHT;
1203
1204         setmodel(self.vehicle_hudmodel, veh.hud_model);
1205         setmodel(self.vehicle_viewport, "null");
1206
1207         if(veh.head_model != "")
1208         {
1209                 setmodel(self.tur_head, veh.head_model);
1210                 setattachment(self.tur_head, self, veh.tag_head);
1211                 setattachment(self.vehicle_hudmodel, self.tur_head, veh.tag_hud);
1212                 setattachment(self.vehicle_viewport, self.vehicle_hudmodel, veh.tag_view);
1213         }
1214         else
1215         {
1216                 setattachment(self.tur_head, self, "");
1217                 setattachment(self.vehicle_hudmodel, self, veh.tag_hud);
1218                 setattachment(self.vehicle_viewport, self.vehicle_hudmodel, veh.tag_view);
1219         }
1220
1221         setsize(self, veh.mins, veh.maxs);
1222
1223         if(!nodrop)
1224         {
1225                 setorigin(self, self.origin);
1226                 tracebox(self.origin + '0 0 100', veh.mins, veh.maxs, self.origin - '0 0 10000', MOVE_WORLDONLY, self);
1227                 setorigin(self, trace_endpos);
1228         }
1229
1230         self.pos1 = self.origin;
1231         self.pos2 = self.angles;
1232         self.tur_head.team = self.team;
1233
1234         VEH_ACTION(vehicle_id, VR_SETUP);
1235
1236         if(autocvar_g_vehicles_delayspawn)
1237                 self.nextthink = time + self.respawntime + (random() * autocvar_g_vehicles_delayspawn_jitter);
1238         else
1239                 self.nextthink = time + game_starttime;
1240
1241         if(MUTATOR_CALLHOOK(VehicleSpawn))
1242                 return FALSE;
1243
1244         return TRUE;
1245 }