1#include "player.qh"
2
3#include "bot/api.qh"
4#include "cheats.qh"
5#include "g_damage.qh"
6#include "g_subs.qh"
7#include "miscfunctions.qh"
8#include "portals.qh"
9#include "teamplay.qh"
10#include "weapons/throwing.qh"
11#include "command/common.qh"
12#include "../common/state.qh"
13#include "../common/anim.qh"
14#include "../common/animdecide.qh"
15#include "../common/csqcmodel_settings.qh"
16#include "../common/deathtypes/all.qh"
17#include "../common/triggers/subs.qh"
18#include "../common/playerstats.qh"
19#include "../lib/csqcmodel/sv_model.qh"
20
21#include "../common/minigames/sv_minigames.qh"
22
23#include "../common/physics/player.qh"
24#include "../common/effects/qc/all.qh"
25#include "../common/mutators/mutator/waypoints/waypointsprites.qh"
26#include "../common/triggers/include.qh"
27#include "../common/wepent.qh"
28
29#include "weapons/weaponstats.qh"
30
31#include "../common/animdecide.qh"
32
33void Drop_Special_Items(entity player)
34{
35	// called when the player has become stuck or frozen
36	// so objective items aren't stuck with the player
37
38	MUTATOR_CALLHOOK(DropSpecialItems, player);
39}
40
41void CopyBody_Think(entity this)
42{
43	if(this.CopyBody_nextthink && time > this.CopyBody_nextthink)
44	{
45		this.CopyBody_think(this);
46		if(wasfreed(this))
47			return;
48		this.CopyBody_nextthink = this.nextthink;
49		this.CopyBody_think = getthink(this);
50		setthink(this, CopyBody_Think);
51	}
52	CSQCMODEL_AUTOUPDATE(this);
53	this.nextthink = time;
54}
55void CopyBody(entity this, float keepvelocity)
56{
57	if (this.effects & EF_NODRAW)
58		return;
59	entity clone = new(body);
60	clone.enemy = this;
61	clone.lip = this.lip;
62	clone.colormap = this.colormap;
63	clone.iscreature = this.iscreature;
64	clone.teleportable = this.teleportable;
65	clone.damagedbycontents = this.damagedbycontents;
66	if(clone.damagedbycontents)
67		IL_PUSH(g_damagedbycontents, clone);
68	clone.angles = this.angles;
69	clone.v_angle = this.v_angle;
70	clone.avelocity = this.avelocity;
71	clone.damageforcescale = this.damageforcescale;
72	clone.effects = this.effects;
73	clone.glowmod = this.glowmod;
74	clone.event_damage = this.event_damage;
75	clone.anim_state = this.anim_state;
76	clone.anim_time = this.anim_time;
77	clone.anim_lower_action = this.anim_lower_action;
78	clone.anim_lower_time = this.anim_lower_time;
79	clone.anim_upper_action = this.anim_upper_action;
80	clone.anim_upper_time = this.anim_upper_time;
81	clone.anim_implicit_state = this.anim_implicit_state;
82	clone.anim_implicit_time = this.anim_implicit_time;
83	clone.anim_lower_implicit_action = this.anim_lower_implicit_action;
84	clone.anim_lower_implicit_time = this.anim_lower_implicit_time;
85	clone.anim_upper_implicit_action = this.anim_upper_implicit_action;
86	clone.anim_upper_implicit_time = this.anim_upper_implicit_time;
87	clone.dphitcontentsmask = this.dphitcontentsmask;
88	clone.death_time = this.death_time;
89	clone.pain_finished = this.pain_finished;
90	clone.health = this.health;
91	clone.armorvalue = this.armorvalue;
92	clone.armortype = this.armortype;
93	clone.model = this.model;
94	clone.modelindex = this.modelindex;
95	clone.skin = this.skin;
96	clone.species = this.species;
97	clone.move_qcphysics = false; // don't run gamecode logic on clones, too many
98	set_movetype(clone, this.move_movetype);
99	clone.solid = this.solid;
100	clone.ballistics_density = this.ballistics_density;
101	clone.takedamage = this.takedamage;
102	setcefc(clone, getcefc(this));
103	clone.uncustomizeentityforclient = this.uncustomizeentityforclient;
104	clone.uncustomizeentityforclient_set = this.uncustomizeentityforclient_set;
105	if (keepvelocity == 1)
106		clone.velocity = this.velocity;
107	clone.oldvelocity = clone.velocity;
108	clone.alpha = this.alpha;
109	clone.fade_time = this.fade_time;
110	clone.fade_rate = this.fade_rate;
111	//clone.weapon = this.weapon;
112	setorigin(clone, this.origin);
113	setsize(clone, this.mins, this.maxs);
114	clone.prevorigin = this.origin;
115	clone.reset = SUB_Remove;
116	clone._ps = this._ps;
117
118	Drag_MoveDrag(this, clone);
119
120	if(clone.colormap <= maxclients && clone.colormap > 0)
121		clone.colormap = 1024 + this.clientcolors;
122
123	CSQCMODEL_AUTOINIT(clone);
124	clone.CopyBody_nextthink = this.nextthink;
125	clone.CopyBody_think = getthink(this);
126	clone.nextthink = time;
127	setthink(clone, CopyBody_Think);
128	// "bake" the current animation frame for clones (they don't get clientside animation)
129	animdecide_load_if_needed(clone);
130	animdecide_setframes(clone, false, frame, frame1time, frame2, frame2time);
131
132	IL_PUSH(g_clones, clone);
133
134	MUTATOR_CALLHOOK(CopyBody, this, clone, keepvelocity);
135}
136
137void player_setupanimsformodel(entity this)
138{
139	// load animation info
140	animdecide_load_if_needed(this);
141	animdecide_setstate(this, 0, false);
142}
143
144void player_anim(entity this)
145{
146	int deadbits = (this.anim_state & (ANIMSTATE_DEAD1 | ANIMSTATE_DEAD2));
147	if(IS_DEAD(this)) {
148		if (!deadbits) {
149			// Decide on which death animation to use.
150			if(random() < 0.5)
151				deadbits = ANIMSTATE_DEAD1;
152			else
153				deadbits = ANIMSTATE_DEAD2;
154		}
155	} else {
156		// Clear a previous death animation.
157		deadbits = 0;
158	}
159	int animbits = deadbits;
160	if(STAT(FROZEN, this))
161		animbits |= ANIMSTATE_FROZEN;
162	if(this.move_movetype == MOVETYPE_FOLLOW)
163		animbits |= ANIMSTATE_FOLLOW;
164	if(this.crouch)
165		animbits |= ANIMSTATE_DUCK;
166	animdecide_setstate(this, animbits, false);
167	animdecide_setimplicitstate(this, IS_ONGROUND(this));
168}
169
170void PlayerCorpseDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
171{
172	float take, save;
173	vector v;
174	Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, this, attacker);
175
176	// damage resistance (ignore most of the damage from a bullet or similar)
177	damage = max(damage - 5, 1);
178
179	v = healtharmor_applydamage(this.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage);
180	take = v.x;
181	save = v.y;
182
183	if(sound_allowed(MSG_BROADCAST, attacker))
184	{
185		if (save > 10)
186			sound (this, CH_SHOTS, SND_ARMORIMPACT, VOL_BASE, ATTEN_NORM);
187		else if (take > 30)
188			sound (this, CH_SHOTS, SND_BODYIMPACT2, VOL_BASE, ATTEN_NORM);
189		else if (take > 10)
190			sound (this, CH_SHOTS, SND_BODYIMPACT1, VOL_BASE, ATTEN_NORM);
191	}
192
193	if (take > 50)
194		Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, this, attacker);
195	if (take > 100)
196		Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, this, attacker);
197
198	this.armorvalue = this.armorvalue - save;
199	this.health = this.health - take;
200	// pause regeneration for 5 seconds
201	this.pauseregen_finished = max(this.pauseregen_finished, time + autocvar_g_balance_pause_health_regen);
202
203	this.dmg_save = this.dmg_save + save;//max(save - 10, 0);
204	this.dmg_take = this.dmg_take + take;//max(take - 10, 0);
205	this.dmg_inflictor = inflictor;
206
207	if (this.health <= -autocvar_sv_gibhealth && this.alpha >= 0)
208	{
209		// don't use any animations as a gib
210		this.frame = 0;
211		// view just above the floor
212		this.view_ofs = '0 0 4';
213
214		Violence_GibSplash(this, 1, 1, attacker);
215		this.alpha = -1;
216		this.solid = SOLID_NOT; // restore later
217		this.takedamage = DAMAGE_NO; // restore later
218		if(this.damagedbycontents)
219			IL_REMOVE(g_damagedbycontents, this);
220		this.damagedbycontents = false;
221	}
222}
223
224void calculate_player_respawn_time(entity this)
225{
226	if(g_ca)
227		return;
228
229	float gametype_setting_tmp;
230	float sdelay_max = GAMETYPE_DEFAULTED_SETTING(respawn_delay_max);
231	float sdelay_small = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small);
232	float sdelay_large = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large);
233	float sdelay_small_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small_count);
234	float sdelay_large_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large_count);
235	float waves = GAMETYPE_DEFAULTED_SETTING(respawn_waves);
236
237	float pcount = 1;  // Include myself whether or not team is already set right and I'm a "player".
238	if (teamplay)
239	{
240		FOREACH_CLIENT(IS_PLAYER(it) && it != this, LAMBDA(
241			if(it.team == this.team)
242				++pcount;
243		));
244		if (sdelay_small_count == 0)
245			sdelay_small_count = 1;
246		if (sdelay_large_count == 0)
247			sdelay_large_count = 1;
248	}
249	else
250	{
251		FOREACH_CLIENT(IS_PLAYER(it) && it != this, LAMBDA(
252			++pcount;
253		));
254		if (sdelay_small_count == 0)
255		{
256			if (g_cts)
257			{
258				// Players play independently. No point in requiring enemies.
259				sdelay_small_count = 1;
260			}
261			else
262			{
263				// Players play AGAINST each other. Enemies required.
264				sdelay_small_count = 2;
265			}
266		}
267		if (sdelay_large_count == 0)
268		{
269			if (g_cts)
270			{
271				// Players play independently. No point in requiring enemies.
272				sdelay_large_count = 1;
273			}
274			else
275			{
276				// Players play AGAINST each other. Enemies required.
277				sdelay_large_count = 2;
278			}
279		}
280	}
281
282	float sdelay;
283
284	if (pcount <= sdelay_small_count)
285		sdelay = sdelay_small;
286	else if (pcount >= sdelay_large_count)
287		sdelay = sdelay_large;
288	else  // NOTE: this case implies sdelay_large_count > sdelay_small_count.
289		sdelay = sdelay_small + (sdelay_large - sdelay_small) * (pcount - sdelay_small_count) / (sdelay_large_count - sdelay_small_count);
290
291	if(waves)
292		this.respawn_time = ceil((time + sdelay) / waves) * waves;
293	else
294		this.respawn_time = time + sdelay;
295
296	if(sdelay < sdelay_max)
297		this.respawn_time_max = time + sdelay_max;
298	else
299		this.respawn_time_max = this.respawn_time;
300
301	if((sdelay + waves >= 5.0) && (this.respawn_time - time > 1.75))
302		this.respawn_countdown = 10; // first number to count down from is 10
303	else
304		this.respawn_countdown = -1; // do not count down
305
306	if(autocvar_g_forced_respawn)
307		this.respawn_flags = this.respawn_flags | RESPAWN_FORCE;
308}
309
310void PlayerDamage(entity this, entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
311{
312	float take, save, dh, da;
313	vector v;
314	float valid_damage_for_weaponstats;
315	float excess;
316
317	dh = max(this.health, 0);
318	da = max(this.armorvalue, 0);
319
320	if(!DEATH_ISSPECIAL(deathtype))
321	{
322		damage *= bound(1.0, this.cvar_cl_handicap, 10.0);
323		if(this != attacker)
324			damage /= bound(1.0, attacker.cvar_cl_handicap, 10.0);
325	}
326
327	if (time < this.spawnshieldtime && autocvar_g_spawnshield_blockdamage < 1)
328		damage *= 1 - max(0, autocvar_g_spawnshield_blockdamage);
329
330	if(DEATH_ISWEAPON(deathtype, WEP_TUBA))
331	{
332		// tuba causes blood to come out of the ears
333		vector ear1, ear2;
334		vector d;
335		float f;
336		ear1 = this.origin;
337		ear1_z += 0.125 * this.view_ofs.z + 0.875 * this.maxs.z; // 7/8
338		ear2 = ear1;
339		makevectors(this.angles);
340		ear1 += v_right * -10;
341		ear2 += v_right * +10;
342		d = inflictor.origin - this.origin;
343		if (d)
344			f = (d * v_right) / vlen(d); // this is cos of angle of d and v_right!
345		else
346			f = 0;  // Assum ecenter.
347		force = v_right * vlen(force);
348		Violence_GibSplash_At(ear1, force * -1, 2, bound(0, damage, 25) / 2 * (0.5 - 0.5 * f), this, attacker);
349		Violence_GibSplash_At(ear2, force,      2, bound(0, damage, 25) / 2 * (0.5 + 0.5 * f), this, attacker);
350		if(f > 0)
351		{
352			hitloc = ear1;
353			force = force * -1;
354		}
355		else
356		{
357			hitloc = ear2;
358			// force is already good
359		}
360	}
361	else
362		Violence_GibSplash_At(hitloc, force, 2, bound(0, damage, 200) / 16, this, attacker);
363
364	v = healtharmor_applydamage(this.armorvalue, autocvar_g_balance_armor_blockpercent, deathtype, damage);
365	take = v.x;
366	save = v.y;
367
368	if(attacker == this)
369	{
370		// don't reset pushltime for this damage as it may be an attempt to
371		// escape a lava pit or similar
372		//this.pushltime = 0;
373		this.istypefrag = 0;
374	}
375	else if(IS_PLAYER(attacker))
376	{
377		this.pusher = attacker;
378		this.pushltime = time + autocvar_g_maxpushtime;
379		this.istypefrag = PHYS_INPUT_BUTTON_CHAT(this);
380	}
381	else if(time < this.pushltime)
382	{
383		attacker = this.pusher;
384		this.pushltime = max(this.pushltime, time + 0.6);
385	}
386	else
387	{
388		this.pushltime = 0;
389		this.istypefrag = 0;
390	}
391
392	MUTATOR_CALLHOOK(PlayerDamage_SplitHealthArmor, inflictor, attacker, this, force, take, save, deathtype, damage);
393	take = bound(0, M_ARGV(4, float), this.health);
394	save = bound(0, M_ARGV(5, float), this.armorvalue);
395	excess = max(0, damage - take - save);
396
397	if(sound_allowed(MSG_BROADCAST, attacker))
398	{
399		if (save > 10)
400			sound (this, CH_SHOTS, SND_ARMORIMPACT, VOL_BASE, ATTEN_NORM);
401		else if (take > 30)
402			sound (this, CH_SHOTS, SND_BODYIMPACT2, VOL_BASE, ATTEN_NORM);
403		else if (take > 10)
404			sound (this, CH_SHOTS, SND_BODYIMPACT1, VOL_BASE, ATTEN_NORM); // FIXME possibly remove them?
405	}
406
407	if (take > 50)
408		Violence_GibSplash_At(hitloc, force * -0.1, 3, 1, this, attacker);
409	if (take > 100)
410		Violence_GibSplash_At(hitloc, force * -0.2, 3, 1, this, attacker);
411
412	if (time >= this.spawnshieldtime || autocvar_g_spawnshield_blockdamage < 1)
413	{
414		if (!(this.flags & FL_GODMODE))
415		{
416			this.armorvalue = this.armorvalue - save;
417			this.health = this.health - take;
418			// pause regeneration for 5 seconds
419			if(take)
420				this.pauseregen_finished = max(this.pauseregen_finished, time + autocvar_g_balance_pause_health_regen);
421
422			if (time > this.pain_finished)		//Don't switch pain sequences like crazy
423			{
424				this.pain_finished = time + 0.5;	//Supajoe
425
426				if(autocvar_sv_gentle < 1) {
427					if(this.classname != "body") // pain anim is BORKED on our ZYMs, FIXME remove this once we have good models
428					{
429						if (!this.animstate_override)
430						{
431							if (random() > 0.5)
432								animdecide_setaction(this, ANIMACTION_PAIN1, true);
433							else
434								animdecide_setaction(this, ANIMACTION_PAIN2, true);
435						}
436					}
437
438					if(sound_allowed(MSG_BROADCAST, attacker))
439					if(this.health < 25 || !(DEATH_WEAPONOF(deathtype).spawnflags & WEP_FLAG_CANCLIMB) || take > 20 || attacker != this)
440					if(this.health > 1)
441					// exclude pain sounds for laserjumps as long as you aren't REALLY low on health and would die of the next two
442					{
443						if(deathtype == DEATH_FALL.m_id)
444							PlayerSound(this, playersound_fall, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
445						else if(this.health > 75)
446							PlayerSound(this, playersound_pain100, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
447						else if(this.health > 50)
448							PlayerSound(this, playersound_pain75, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
449						else if(this.health > 25)
450							PlayerSound(this, playersound_pain50, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
451						else
452							PlayerSound(this, playersound_pain25, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
453					}
454				}
455			}
456
457			// throw off bot aim temporarily
458			float shake;
459			if(IS_BOT_CLIENT(this) && this.health >= 1)
460			{
461				shake = damage * 5 / (bound(0,skill,100) + 1);
462				this.v_angle_x = this.v_angle.x + (random() * 2 - 1) * shake;
463				this.v_angle_y = this.v_angle.y + (random() * 2 - 1) * shake;
464				this.v_angle_x = bound(-90, this.v_angle.x, 90);
465			}
466		}
467		else
468			this.max_armorvalue += (save + take);
469	}
470	this.dmg_save = this.dmg_save + save;//max(save - 10, 0);
471	this.dmg_take = this.dmg_take + take;//max(take - 10, 0);
472	this.dmg_inflictor = inflictor;
473
474	if (this != attacker) {
475		float realdmg = damage - excess;
476		if (IS_PLAYER(attacker)) {
477			PlayerScore_Add(attacker, SP_DMG, realdmg);
478		}
479		if (IS_PLAYER(this)) {
480			PlayerScore_Add(this, SP_DMGTAKEN, realdmg);
481		}
482	}
483
484	bool abot = (IS_BOT_CLIENT(attacker));
485	bool vbot = (IS_BOT_CLIENT(this));
486
487	valid_damage_for_weaponstats = 0;
488	Weapon awep = WEP_Null;
489	.entity weaponentity = weaponentities[0]; // TODO: unhardcode
490
491	if(vbot || IS_REAL_CLIENT(this))
492	if(abot || IS_REAL_CLIENT(attacker))
493	if(attacker && this != attacker)
494	if(DIFF_TEAM(this, attacker))
495	{
496		if(DEATH_ISSPECIAL(deathtype))
497			awep = attacker.(weaponentity).m_weapon;
498		else
499			awep = DEATH_WEAPONOF(deathtype);
500		valid_damage_for_weaponstats = 1;
501	}
502
503	dh = dh - max(this.health, 0);
504	da = da - max(this.armorvalue, 0);
505	if(valid_damage_for_weaponstats)
506	{
507		WeaponStats_LogDamage(awep.m_id, abot, this.(weaponentity).m_weapon.m_id, vbot, dh + da);
508	}
509	if (damage)
510	{
511		MUTATOR_CALLHOOK(PlayerDamaged, attacker, this, dh, da, hitloc, deathtype, damage);
512	}
513
514	if (this.health < 1)
515	{
516		float defer_ClientKill_Now_TeamChange;
517		defer_ClientKill_Now_TeamChange = false;
518
519		if(this.alivetime)
520		{
521			PS_GR_P_ADDVAL(this, PLAYERSTATS_ALIVETIME, time - this.alivetime);
522			this.alivetime = 0;
523		}
524
525		if(valid_damage_for_weaponstats)
526			WeaponStats_LogKill(awep.m_id, abot, this.(weaponentity).m_weapon.m_id, vbot);
527
528		if(autocvar_sv_gentle < 1)
529		if(sound_allowed(MSG_BROADCAST, attacker))
530		{
531			if(deathtype == DEATH_DROWN.m_id)
532				PlayerSound(this, playersound_drown, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
533			else
534				PlayerSound(this, playersound_death, CH_PAIN, VOL_BASE, VOICETYPE_PLAYERSOUND);
535		}
536
537		// get rid of kill indicator
538		if(this.killindicator)
539		{
540			delete(this.killindicator);
541			this.killindicator = NULL;
542			if(this.killindicator_teamchange)
543				defer_ClientKill_Now_TeamChange = true;
544
545			if(this.classname == "body")
546			if(deathtype == DEATH_KILL.m_id)
547			{
548				// for the lemmings fans, a small harmless explosion
549				Send_Effect(EFFECT_ROCKET_EXPLODE, this.origin, '0 0 0', 1);
550			}
551		}
552
553		// print an obituary message
554		if(this.classname != "body")
555			Obituary (attacker, inflictor, this, deathtype);
556
557        // increment frag counter for used weapon type
558        Weapon w = DEATH_WEAPONOF(deathtype);
559		if(w != WEP_Null && accuracy_isgooddamage(attacker, this))
560			attacker.accuracy.(accuracy_frags[w.m_id-1]) += 1;
561
562		MUTATOR_CALLHOOK(PlayerDies, inflictor, attacker, this, deathtype, damage);
563		excess = M_ARGV(4, float);
564
565		Weapon wep = this.(weaponentity).m_weapon;
566		for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
567		{
568			.entity went = weaponentities[slot];
569			wep.wr_playerdeath(wep, this, went);
570		}
571
572		RemoveGrapplingHooks(this);
573
574		Portal_ClearAllLater(this);
575
576		this.fixangle = true;
577
578		if(defer_ClientKill_Now_TeamChange)
579			ClientKill_Now_TeamChange(this); // can turn player into spectator
580
581		// player could have been miraculously resuscitated ;)
582		// e.g. players in freezetag get frozen, they don't really die
583		if(this.health >= 1 || !(IS_PLAYER(this) || this.classname == "body"))
584			return;
585
586		// when we get here, player actually dies
587
588		Unfreeze(this); // remove any icy remains
589		this.health = 0; // Unfreeze resets health, so we need to set it back
590
591		// clear waypoints
592		WaypointSprite_PlayerDead(this);
593		// throw a weapon
594		for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
595		{
596			.entity went = weaponentities[slot];
597			SpawnThrownWeapon(this, this.origin + (this.mins + this.maxs) * 0.5, this.(went).m_weapon, went);
598		}
599
600		// become fully visible
601		this.alpha = default_player_alpha;
602		// make the corpse upright (not tilted)
603		this.angles_x = 0;
604		this.angles_z = 0;
605		// don't spin
606		this.avelocity = '0 0 0';
607		// view from the floor
608		this.view_ofs = '0 0 -8';
609		// toss the corpse
610		set_movetype(this, MOVETYPE_TOSS);
611		// shootable corpse
612		this.solid = SOLID_CORPSE;
613		this.ballistics_density = autocvar_g_ballistics_density_corpse;
614		// don't stick to the floor
615		UNSET_ONGROUND(this);
616		// dying animation
617		this.deadflag = DEAD_DYING;
618
619		// when to allow respawn
620		calculate_player_respawn_time(this);
621
622		this.death_time = time;
623		if (random() < 0.5)
624			animdecide_setstate(this, this.anim_state | ANIMSTATE_DEAD1, true);
625		else
626			animdecide_setstate(this, this.anim_state | ANIMSTATE_DEAD2, true);
627		if (this.maxs.z > 5)
628		{
629			this.maxs_z = 5;
630			setsize(this, this.mins, this.maxs);
631		}
632		// set damage function to corpse damage
633		this.event_damage = PlayerCorpseDamage;
634		// call the corpse damage function just in case it wants to gib
635		this.event_damage(this, inflictor, attacker, excess, deathtype, hitloc, force);
636
637		// set up to fade out later
638		SUB_SetFade (this, time + 6 + random (), 1);
639		// reset body think wrapper broken by SUB_SetFade
640		if(this.classname == "body" && getthink(this) != CopyBody_Think) {
641			this.CopyBody_think = getthink(this);
642			this.CopyBody_nextthink = this.nextthink;
643			setthink(this, CopyBody_Think);
644			this.nextthink = time;
645		}
646
647		if(autocvar_sv_gentle > 0 || autocvar_ekg || this.classname == "body") {
648			// remove corpse
649			// clones don't run any animation code any more, so we must gib them when they die :(
650			PlayerCorpseDamage(this, inflictor, attacker, autocvar_sv_gibhealth+1.0, deathtype, hitloc, force);
651		}
652
653		// reset fields the weapons may use just in case
654		FOREACH(Weapons, it != WEP_Null, LAMBDA(
655			it.wr_resetplayer(it, this);
656			for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
657			{
658				ATTACK_FINISHED_FOR(this, it.m_id, slot) = 0;
659			}
660		));
661	}
662}
663
664void MoveToTeam(entity client, int team_colour, int type)
665{
666	int lockteams_backup = lockteams;  // backup any team lock
667	lockteams = 0;  // disable locked teams
668	TeamchangeFrags(client);  // move the players frags
669	SetPlayerColors(client, team_colour - 1);  // set the players colour
670	Damage(client, client, client, 100000, DEATH_AUTOTEAMCHANGE.m_id, client.origin, '0 0 0');  // kill the player
671	lockteams = lockteams_backup;  // restore the team lock
672	LogTeamchange(client.playerid, client.team, type);
673}
674
675/** print(), but only print if the server is not local */
676void dedicated_print(string input)
677{
678	if (server_is_dedicated) print(input);
679}
680
681void PrintToChat(entity player, string text)
682{
683	text = strcat("\{1}^7", text, "\n");
684	sprint(player, text);
685}
686
687/**
688 * message "": do not say, just test flood control
689 * return value:
690 *   1 = accept
691 *   0 = reject
692 *  -1 = fake accept
693 */
694int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodcontrol)
695{
696	if (!teamsay && !privatesay && substring(msgin, 0, 1) == " ")
697        msgin = substring(msgin, 1, -1); // work around DP say bug (say_team does not have this!)
698
699	msgin = formatmessage(source, msgin);
700
701    string colorstr;
702	if (!IS_PLAYER(source))
703		colorstr = "^0"; // black for spectators
704	else if(teamplay)
705		colorstr = Team_ColorCode(source.team);
706	else
707	{
708		colorstr = "";
709		teamsay = false;
710	}
711
712	if(game_stopped)
713		teamsay = false;
714
715    if (!source) {
716		colorstr = "";
717		teamsay = false;
718    }
719
720	if(msgin != "")
721		msgin = trigger_magicear_processmessage_forallears(source, teamsay, privatesay, msgin);
722
723	/*
724	 * using bprint solves this... me stupid
725	// how can we prevent the message from appearing in a listen server?
726	// for now, just give "say" back and only handle say_team
727	if(!teamsay)
728	{
729		clientcommand(source, strcat("say ", msgin));
730		return;
731	}
732	*/
733
734    string namestr = "";
735    if (source)
736        namestr = playername(source, autocvar_g_chat_teamcolors);
737
738    string colorprefix = (strdecolorize(namestr) == namestr) ? "^3" : "^7";
739
740    string msgstr, cmsgstr;
741    string privatemsgprefix = string_null;
742    int privatemsgprefixlen = 0;
743	if (msgin == "") {
744        msgstr = cmsgstr = "";
745	} else {
746		if(privatesay)
747		{
748			msgstr = strcat("\{1}\{13}* ", colorprefix, namestr, "^3 tells you: ^7");
749			privatemsgprefixlen = strlen(msgstr);
750			msgstr = strcat(msgstr, msgin);
751			cmsgstr = strcat(colorstr, colorprefix, namestr, "^3 tells you:\n^7", msgin);
752			privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", playername(privatesay, autocvar_g_chat_teamcolors), ": ^7");
753		}
754		else if(teamsay)
755		{
756			if(strstrofs(msgin, "/me", 0) >= 0)
757			{
758				//msgin = strreplace("/me", "", msgin);
759				//msgin = substring(msgin, 3, strlen(msgin));
760				msgin = strreplace("/me", strcat(colorstr, "(", colorprefix, namestr, colorstr, ")^7"), msgin);
761				msgstr = strcat("\{1}\{13}^4* ", "^7", msgin);
762			}
763			else
764				msgstr = strcat("\{1}\{13}", colorstr, "(", colorprefix, namestr, colorstr, ") ^7", msgin);
765			cmsgstr = strcat(colorstr, "(", colorprefix, namestr, colorstr, ")\n^7", msgin);
766		}
767		else
768		{
769			if(strstrofs(msgin, "/me", 0) >= 0)
770			{
771				//msgin = strreplace("/me", "", msgin);
772				//msgin = substring(msgin, 3, strlen(msgin));
773				msgin = strreplace("/me", strcat(colorprefix, namestr), msgin);
774				msgstr = strcat("\{1}^4* ", "^7", msgin);
775			}
776			else {
777                msgstr = "\{1}";
778                msgstr = strcat(msgstr, (namestr != "") ? strcat(colorprefix, namestr, "^7: ") : "^7");
779                msgstr = strcat(msgstr, msgin);
780            }
781			cmsgstr = "";
782		}
783		msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint
784	}
785
786	string fullmsgstr = msgstr;
787	string fullcmsgstr = cmsgstr;
788
789	// FLOOD CONTROL
790	int flood = 0;
791	var .float flood_field = floodcontrol_chat;
792	if(floodcontrol)
793	{
794		float flood_spl;
795		float flood_burst;
796		float flood_lmax;
797		float lines;
798		if(privatesay)
799		{
800			flood_spl = autocvar_g_chat_flood_spl_tell;
801			flood_burst = autocvar_g_chat_flood_burst_tell;
802			flood_lmax = autocvar_g_chat_flood_lmax_tell;
803			flood_field = floodcontrol_chattell;
804		}
805		else if(teamsay)
806		{
807			flood_spl = autocvar_g_chat_flood_spl_team;
808			flood_burst = autocvar_g_chat_flood_burst_team;
809			flood_lmax = autocvar_g_chat_flood_lmax_team;
810			flood_field = floodcontrol_chatteam;
811		}
812		else
813		{
814			flood_spl = autocvar_g_chat_flood_spl;
815			flood_burst = autocvar_g_chat_flood_burst;
816			flood_lmax = autocvar_g_chat_flood_lmax;
817			flood_field = floodcontrol_chat;
818		}
819		flood_burst = max(0, flood_burst - 1);
820		// to match explanation in default.cfg, a value of 3 must allow three-line bursts and not four!
821
822		// do flood control for the default line size
823		if(msgstr != "")
824		{
825			getWrappedLine_remaining = msgstr;
826			msgstr = "";
827			lines = 0;
828			while(getWrappedLine_remaining && (!flood_lmax || lines <= flood_lmax))
829			{
830				msgstr = strcat(msgstr, " ", getWrappedLineLen(82.4289758859709, strlennocol)); // perl averagewidth.pl < gfx/vera-sans.width
831				++lines;
832			}
833			msgstr = substring(msgstr, 1, strlen(msgstr) - 1);
834
835			if(getWrappedLine_remaining != "")
836			{
837				msgstr = strcat(msgstr, "\n");
838				flood = 2;
839			}
840
841			if (time >= source.(flood_field))
842			{
843				source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + lines * flood_spl;
844			}
845			else
846			{
847				flood = 1;
848				msgstr = fullmsgstr;
849			}
850		}
851		else
852		{
853			if (time >= source.(flood_field))
854				source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + flood_spl;
855			else
856				flood = 1;
857		}
858
859		if (timeout_status == TIMEOUT_ACTIVE) // when game is paused, no flood protection
860			source.(flood_field) = flood = 0;
861	}
862
863    string sourcemsgstr, sourcecmsgstr;
864	if(flood == 2) // cannot happen for empty msgstr
865	{
866		if(autocvar_g_chat_flood_notify_flooder)
867		{
868			sourcemsgstr = strcat(msgstr, "\n^3FLOOD CONTROL: ^7message too long, trimmed\n");
869			sourcecmsgstr = "";
870		}
871		else
872		{
873			sourcemsgstr = fullmsgstr;
874			sourcecmsgstr = fullcmsgstr;
875		}
876		cmsgstr = "";
877	}
878	else
879	{
880		sourcemsgstr = msgstr;
881		sourcecmsgstr = cmsgstr;
882	}
883
884	if (!privatesay && source && !IS_PLAYER(source))
885	{
886		if (!game_stopped)
887		if (teamsay || (autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage))
888			teamsay = -1; // spectators
889	}
890
891	if(flood)
892		LOG_INFO("NOTE: ", playername(source, true), "^7 is flooding.\n");
893
894	// build sourcemsgstr by cutting off a prefix and replacing it by the other one
895	if(privatesay)
896		sourcemsgstr = strcat(privatemsgprefix, substring(sourcemsgstr, privatemsgprefixlen, -1));
897
898    int ret;
899	if(source.muted)
900	{
901		// always fake the message
902		ret = -1;
903	}
904	else if(flood == 1)
905	{
906		if (autocvar_g_chat_flood_notify_flooder)
907		{
908			sprint(source, strcat("^3FLOOD CONTROL: ^7wait ^1", ftos(source.(flood_field) - time), "^3 seconds\n"));
909			ret = 0;
910		}
911		else
912			ret = -1;
913	}
914	else
915	{
916		ret = 1;
917	}
918
919	if (privatesay && source && !IS_PLAYER(source))
920	{
921		if (!game_stopped)
922		if ((privatesay && !IS_PLAYER(privatesay)) || (autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage))
923			ret = -1; // just hide the message completely
924	}
925
926	MUTATOR_CALLHOOK(ChatMessage, source, ret);
927	ret = M_ARGV(1, int);
928
929	if(sourcemsgstr != "" && ret != 0)
930	{
931		if(ret < 0) // faked message, because the player is muted
932		{
933			sprint(source, sourcemsgstr);
934			if(sourcecmsgstr != "" && !privatesay)
935				centerprint(source, sourcecmsgstr);
936		}
937		else if(privatesay) // private message, between 2 people only
938		{
939			sprint(source, sourcemsgstr);
940			if (!autocvar_g_chat_tellprivacy) { dedicated_print(msgstr); } // send to server console too if "tellprivacy" is disabled
941			if(!MUTATOR_CALLHOOK(ChatMessageTo, privatesay, source))
942			{
943				sprint(privatesay, msgstr);
944				if(cmsgstr != "")
945					centerprint(privatesay, cmsgstr);
946			}
947		}
948		else if ( teamsay && source.active_minigame )
949		{
950			sprint(source, sourcemsgstr);
951			dedicated_print(msgstr); // send to server console too
952			FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && it.active_minigame == source.active_minigame && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), sprint(it, msgstr));
953		}
954		else if(teamsay > 0) // team message, only sent to team mates
955		{
956			sprint(source, sourcemsgstr);
957			dedicated_print(msgstr); // send to server console too
958			if(sourcecmsgstr != "")
959				centerprint(source, sourcecmsgstr);
960			FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it) && it != source && it.team == source.team && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), {
961				sprint(it, msgstr);
962				if(cmsgstr != "")
963					centerprint(it, cmsgstr);
964			});
965		}
966		else if(teamsay < 0) // spectator message, only sent to spectators
967		{
968			sprint(source, sourcemsgstr);
969			dedicated_print(msgstr); // send to server console too
970			FOREACH_CLIENT(!IS_PLAYER(it) && IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), sprint(it, msgstr));
971		}
972		else
973		{
974            if (source) {
975                sprint(source, sourcemsgstr);
976                dedicated_print(msgstr); // send to server console too
977                MX_Say(strcat(playername(source, true), "^7: ", msgin));
978            }
979            FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), sprint(it, msgstr));
980        }
981	}
982
983	return ret;
984}
985