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