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