2023-04-13 17:30:38 +02:00

1655 lines
48 KiB
Plaintext

#using scripts\shared\callbacks_shared;
#using scripts\shared\challenges_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\demo_shared;
#using scripts\shared\gameobjects_shared;
#using scripts\shared\hostmigration_shared;
#using scripts\shared\hud_util_shared;
#using scripts\shared\math_shared;
#using scripts\shared\medals_shared;
#using scripts\shared\popups_shared;
#using scripts\shared\scoreevents_shared;
#using scripts\shared\sound_shared;
#using scripts\shared\util_shared;
#using scripts\mp\gametypes\_battlechatter;
#using scripts\mp\gametypes\_globallogic;
#using scripts\mp\gametypes\_globallogic_audio;
#using scripts\mp\gametypes\_globallogic_score;
#using scripts\mp\gametypes\_globallogic_utils;
#using scripts\mp\gametypes\_hostmigration;
#using scripts\mp\gametypes\_spawning;
#using scripts\mp\gametypes\_spawnlogic;
#using scripts\mp\_challenges;
#using scripts\mp\_util;
/*QUAKED mp_multi_team_spawn (1.0 0.0 0.0) (-16 -16 0) (16 16 72)
Spawns used for use in some multi team game modes to open up other portions of the map for multi team scenarios.*/
#precache( "string", "OBJECTIVES_KOTH" );
#precache( "string", "OBJECTIVES_KOTH_SCORE" );
#precache( "string", "MP_WAITING_FOR_HQ" );
#precache( "string", "MP_KOTH_CAPTURED_BY" );
#precache( "string", "MP_KOTH_CAPTURED_BY_ENEMY" );
#precache( "string", "MP_KOTH_MOVING_IN" );
#precache( "string", "MP_CAPTURING_OBJECTIVE" );
#precache( "string", "MP_KOTH_CONTESTED_BY_ENEMY" );
#precache( "string", "MP_KOTH_AVAILABLE_IN" );
#precache( "string", "MP_CONTROL_KOTH" );
#precache( "string", "MP_CAPTURE_KOTH" );
#precache( "string", "MP_DEFEND_KOTH" );
#precache( "string", "MP_KOTH_AVAILABLE_IN" );
#precache( "string", "MP_HQ_DESPAWN_IN" );
#precache( "string", "MP_HQ_REINFORCEMENTS_IN" );
#precache( "string", "MP_CAPTURING_HQ" );
#precache( "string", "MP_DESTROYING_HQ" );
#precache( "fx", "ui/fx_koth_marker_blue" );
#precache( "fx", "ui/fx_koth_marker_orng" );
#precache( "fx", "ui/fx_koth_marker_neutral" );
#precache( "fx", "ui/fx_koth_marker_contested" );
#precache( "fx", "ui/fx_koth_marker_blue_window" );
#precache( "fx", "ui/fx_koth_marker_orng_window" );
#precache( "fx", "ui/fx_koth_marker_neutral_window" );
#precache( "fx", "ui/fx_koth_marker_contested_window" );
#precache( "objective", "hardpoint" );
function main()
{
globallogic::init();
util::registerTimeLimit( 0, 1440 );
util::registerScoreLimit( 0, 1000 );
util::registerNumLives( 0, 100 );
util::registerRoundSwitch( 0, 9 );
util::registerRoundWinLimit( 0, 10 );
globallogic::registerFriendlyFireDelay( level.gameType, 15, 0, 1440 );
level.teamBased = true;
level.doPrematch = true;
level.overrideTeamScore = true;
level.scoreRoundWinBased = true;
level.kothStartTime = 0;
level.onStartGameType =&onStartGameType;
level.playerSpawnedCB =&koth_playerSpawnedCB;
level.onRoundSwitch =&onRoundSwitch;
level.onPlayerKilled =&onPlayerKilled;
level.onEndGame=&onEndGame;
clientfield::register( "world", "hardpoint", 1, 5, "int" );
clientfield::register( "world", "hardpointteam", 1, 5, "int" );
level.zoneAutoMoveTime = GetGametypeSetting( "autoDestroyTime" );
level.zoneSpawnTime = GetGametypeSetting( "objectiveSpawnTime" );
level.kothMode = GetGametypeSetting( "kothMode" );
level.captureTime = GetGametypeSetting( "captureTime" );
level.destroyTime = GetGametypeSetting( "destroyTime" );
level.delayPlayer = GetGametypeSetting( "delayPlayer" );
level.randomZoneSpawn = GetGametypeSetting( "randomObjectiveLocations" );
level.scorePerPlayer = GetGametypeSetting( "scorePerPlayer" );
level.timePausesWhenInZone = GetGametypeSetting( "timePausesWhenInZone" );
level.iconoffset = (0,0,32);
level.onRespawnDelay =&getRespawnDelay;
gameobjects::register_allowed_gameobject( level.gameType );
globallogic_audio::set_leader_gametype_dialog ( "startHardPoint", "hcStartHardPoint", "objCapture", "objCapture" );
game["objective_gained_sound"] = "mpl_flagcapture_sting_friend";
game["objective_lost_sound"] = "mpl_flagcapture_sting_enemy";
game["objective_contested_sound"] = "mpl_flagreturn_sting";
level.lastDialogTime = 0;
level.zoneSpawnQueue = [];
// Sets the scoreboard columns and determines with data is sent across the network
if ( !SessionModeIsSystemlink() && !SessionModeIsOnlineGame() && IsSplitScreen() )
// local matches only show the first three columns
globallogic::setvisiblescoreboardcolumns( "score", "kills", "objtime", "defends", "deaths" );
else
globallogic::setvisiblescoreboardcolumns( "score", "kills", "deaths", "objtime", "defends" );
/#
// HQ radio triggers are not scoped to exclude koth right now
// going to delete them just so if we render triggers we dont see all these
// TODO in the future is to get them into the automatic game type delete system
trigs = getentarray("radiotrigger", "targetname");
foreach( trig in trigs )
{
trig delete();
}
#/
}
function updateObjectiveHintMessages( defenderTeam, defendMessage, attackMessage )
{
foreach( team in level.teams )
{
if ( defenderTeam == team )
{
game["strings"]["objective_hint_" + team] = defendMessage;
}
else
{
game["strings"]["objective_hint_" + team] = attackMessage;
}
}
}
function updateObjectiveHintMessage( message )
{
foreach( team in level.teams )
{
game["strings"]["objective_hint_" + team] = message;
}
}
function getRespawnDelay()
{
self.lowerMessageOverride = undefined;
if ( !isdefined( level.zone.gameobject ) )
return undefined;
zoneOwningTeam = level.zone.gameobject gameobjects::get_owner_team();
if ( self.pers["team"] == zoneOwningTeam )
{
if ( !isdefined( level.zoneMoveTime ) )
return undefined;
timeRemaining = (level.zoneMoveTime - gettime()) / 1000;
if (!level.playerObjectiveHeldRespawnDelay )
return undefined;
if ( level.playerObjectiveHeldRespawnDelay >= level.zoneAutoMoveTime )
self.lowerMessageOverride = &"MP_WAITING_FOR_HQ";
if ( level.delayPlayer )
{
return min( level.spawnDelay, timeRemaining );
}
else
{
return ceil(timeRemaining);
}
}
}
function onStartGameType()
{
if ( !isdefined( game["switchedsides"] ) )
game["switchedsides"] = false;
if ( game["switchedsides"] )
{
oldAttackers = game["attackers"];
oldDefenders = game["defenders"];
game["attackers"] = oldDefenders;
game["defenders"] = oldAttackers;
}
globallogic_score::resetTeamScores();
foreach( team in level.teams )
{
util::setObjectiveText( team, &"OBJECTIVES_KOTH" );
if ( level.splitscreen )
{
util::setObjectiveScoreText( team, &"OBJECTIVES_KOTH" );
}
else
{
util::setObjectiveScoreText( team, &"OBJECTIVES_KOTH_SCORE" );
}
}
level.kothTotalSecondsInZone = 0;
level.objectiveHintPrepareZone = &"MP_CONTROL_KOTH";
level.objectiveHintCaptureZone = &"MP_CAPTURE_KOTH";
level.objectiveHintDefendHQ = &"MP_DEFEND_KOTH";
if ( level.zoneSpawnTime )
updateObjectiveHintMessage( level.objectiveHintPrepareZone );
else
updateObjectiveHintMessage( level.objectiveHintCaptureZone );
setClientNameMode("auto_change");
// now that the game objects have been deleted place the influencers
spawning::create_map_placed_influencers();
// TODO: HQ spawnpoints
level.spawnMins = ( 0, 0, 0 );
level.spawnMaxs = ( 0, 0, 0 );
foreach( team in level.teams )
{
spawnlogic::add_spawn_points( team, "mp_tdm_spawn" );
spawnlogic::add_spawn_points( team, "mp_multi_team_spawn" );
spawnlogic::place_spawn_points( spawning::getTDMStartSpawnName(team) );
}
spawning::updateAllSpawnPoints();
level.spawn_start = [];
foreach( team in level.teams )
{
level.spawn_start[ team ] = spawnlogic::get_spawnpoint_array( spawning::getTDMStartSpawnName(team) );
}
level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs );
setMapCenter( level.mapCenter );
spawnpoint = spawnlogic::get_random_intermission_point();
setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles );
level.spawn_all = spawnlogic::get_spawnpoint_array( "mp_tdm_spawn" );
if ( !level.spawn_all.size )
{
/#
println("^1No mp_tdm_spawn spawnpoints in level!");
#/
callback::abort_level();
return;
}
thread SetupZones();
updateGametypeDvars();
thread KothMainLoop();
}
function pause_time()
{
if ( level.timePausesWhenInZone )
{
globallogic_utils::pauseTimer();
level.timerPaused = true;
}
}
function resume_time()
{
if ( level.timePausesWhenInZone )
{
globallogic_utils::resumeTimerDiscardOverride( level.kothTotalSecondsInZone * 1000 );
level.timerPaused = false;
}
}
function updateGametypeDvars()
{
level.playerCaptureLPM = GetGametypeSetting( "maxPlayerEventsPerMinute" );
level.timePausesWhenInZone = GetGametypeSetting( "timePausesWhenInZone" );
}
function spawn_first_zone(delay)
{
// pick next Zone object
if ( level.randomZoneSpawn == 1 )
{
level.zone = GetNextZoneFromQueue();
}
else
{
level.zone = GetFirstZone();
}
if ( isdefined( level.zone ) )
{
/#print("zone spawned: ("+level.zone.trigOrigin[0]+","+level.zone.trigOrigin[1]+","+level.zone.trigOrigin[2]+")");#/
level.zone spawning::enable_influencers(true);
}
level.zone.gameobject.trigger AllowTacticalInsertion( false );
return;
}
function spawn_next_zone()
{
level.zone.gameobject.trigger AllowTacticalInsertion( true );
// pick next Zone object
if ( level.randomZoneSpawn != 0 )
{
level.zone = GetNextZoneFromQueue();
}
else
{
level.zone = GetNextZone();
}
if ( isdefined( level.zone ) )
{
/#print("zone spawned: ("+level.zone.trigOrigin[0]+","+level.zone.trigOrigin[1]+","+level.zone.trigOrigin[2]+")");#/
level.zone spawning::enable_influencers(true);
}
level.zone.gameobject.trigger AllowTacticalInsertion( false );
return;
}
function getNumTouching( )
{
numTouching = 0;
foreach( team in level.teams )
{
numTouching += self.numTouching[team];
}
return numTouching;
}
function toggleZoneEffects( enabled )
{
index = 0;
if ( enabled )
{
index = self.script_index;
}
level clientfield::set( "hardpoint", index );
level clientfield::set( "hardpointteam", 0 );
}
function KothCaptureLoop()
{
level endon("game_ended");
level endon("zone_moved");
level.kothStartTime = gettime();
while( 1 )
{
level.zone.gameobject gameobjects::allow_use( "any" );
level.zone.gameobject gameobjects::set_use_time( level.captureTime );
level.zone.gameobject gameobjects::set_use_text( &"MP_CAPTURING_OBJECTIVE" );
numTouching = level.zone.gameobject getNumTouching( );
level.zone.gameobject gameobjects::set_visible_team( "any" );
level.zone.gameobject gameobjects::set_model_visibility( true );
level.zone.gameobject gameobjects::must_maintain_claim( false );
level.zone.gameobject gameobjects::can_contest_claim( true );
level.zone.gameobject.onUse =&onZoneCapture;
level.zone.gameobject.onBeginUse =&onBeginUse;
level.zone.gameobject.onEndUse =&onEndUse;
level.zone toggleZoneEffects( true );
msg = level util::waittill_any_return( "zone_captured", "zone_destroyed", "game_ended", "zone_moved" );
// this happens if it goes from contested to neutral
if ( msg == "zone_destroyed" )
continue;
ownerTeam = level.zone.gameobject gameobjects::get_owner_team();
foreach( team in level.teams )
{
updateObjectiveHintMessages( ownerTeam, level.objectiveHintDefendHQ, level.objectiveHintCaptureZone );
}
level.zone.gameobject gameobjects::allow_use( "none" );
level.zone.gameobject.onUse = undefined;
level.zone.gameobject.onUnoccupied =&onZoneUnoccupied;
level.zone.gameobject.onContested =&onZoneContested;
level.zone.gameobject.onUncontested =&onZoneUncontested;
level waittill( "zone_destroyed", destroy_team );
if ( !level.kothMode || level.zoneDestroyedByTimer )
break;
thread forceSpawnTeam( ownerTeam );
if ( isdefined( destroy_team ) )
{
level.zone.gameobject gameobjects::set_owner_team( destroy_team );
}
else
{
level.zone.gameobject gameobjects::set_owner_team( "none" );
}
}
}
function KothMainLoop()
{
level endon("game_ended");
level.zoneRevealTime = -100000;
zoneSpawningInStr = &"MP_KOTH_AVAILABLE_IN";
if ( level.kothMode )
{
zoneDestroyedInFriendlyStr = &"MP_HQ_DESPAWN_IN";
zoneDestroyedInEnemyStr = &"MP_KOTH_MOVING_IN";
}
else
{
zoneDestroyedInFriendlyStr = &"MP_HQ_REINFORCEMENTS_IN";
zoneDestroyedInEnemyStr = &"MP_HQ_DESPAWN_IN";
}
spawn_first_zone();
while ( level.inPrematchPeriod )
{wait(.05);};
pause_time();
wait 5;
SetBombTimer( "A", 0 );
setMatchFlag( "bomb_timer_a", 0 );
thread hideTimerDisplayOnGameEnd();
while( 1 )
{
resume_time();
sound::play_on_players( "mp_suitcase_pickup" );
globallogic_audio::leader_dialog( "kothLocated", undefined, undefined, "gamemode_objective", undefined, "kothActiveDialogBuffer" );
level.zone.gameobject gameobjects::set_model_visibility( true );
level.zoneRevealTime = gettime();
if ( level.zoneSpawnTime )
{
level.zone.gameobject gameobjects::set_visible_team( "any" );
level.zone.gameobject gameobjects::set_flags( 1 );
updateObjectiveHintMessage( level.objectiveHintPrepareZone );
setMatchFlag( "bomb_timer_a", 1 );
SetBombTimer( "A", int( gettime() + 1000 + level.zoneSpawnTime * 1000 ) );
wait level.zoneSpawnTime;
level.zone.gameobject gameobjects::set_flags( 0 );
globallogic_audio::leader_dialog( "kothOnline", undefined, undefined, "gamemode_objective", undefined, "kothActiveDialogBuffer" );
}
setMatchFlag( "bomb_timer_a", 0 );
waittillframeend;
updateObjectiveHintMessage( level.objectiveHintCaptureZone );
sound::play_on_players( "mpl_hq_cap_us" );
level.zone.gameobject gameobjects::enable_object();
// Attach the objective to the visuals since it is at the right position and we can't attch it to the koth_zone_center script_origin
Objective_OnEntity( level.zone.gameobject.objectiveID, level.zone.objectiveAnchor );
level.zone.gameobject.captureCount = 0;
if ( level.zoneAutoMoveTime )
{
thread MoveZoneAfterTime( level.zoneAutoMoveTime );
setMatchFlag( "bomb_timer_a", 1 );
SetBombTimer( "A", int( gettime() + 1000 + level.zoneAutoMoveTime * 1000 ) );
}
else
{
level.zoneDestroyedByTimer = false;
}
KothCaptureLoop();
ownerTeam = level.zone.gameobject gameobjects::get_owner_team();
if ( level.zone.gameobject.captureCount == 1 )
{
// Copy touch list so there aren't any threading issues
touchList = [];
touchKeys = GetArrayKeys( level.zone.gameobject.touchList[ownerTeam] );
for ( i = 0 ; i < touchKeys.size ; i++ )
touchList[touchKeys[i]] = level.zone.gameobject.touchList[ownerTeam][touchKeys[i]];
thread give_held_credit( touchList );
}
pause_time();
level.zone spawning::enable_influencers(false);
level.zone.gameobject.lastCaptureTeam = undefined;
level.zone.gameobject gameobjects::disable_object();
level.zone.gameobject gameobjects::allow_use( "none" );
level.zone.gameobject gameobjects::set_owner_team( "neutral" );
level.zone.gameobject gameobjects::set_model_visibility( false );
level.zone.gameobject gameobjects::must_maintain_claim( false );
level.zone toggleZoneEffects( false );
level notify("zone_reset");
setMatchFlag( "bomb_timer_a", 0 );
spawn_next_zone();
wait 0.5;
thread forceSpawnTeam( ownerTeam );
wait 0.5;
}
}
function hideTimerDisplayOnGameEnd()
{
level waittill("game_ended");
setMatchFlag( "bomb_timer_a", 0 );
}
function forceSpawnTeam( team )
{
players = level.players;
for ( i = 0; i < players.size; i++ )
{
player = players[i];
if ( !isdefined( player ) )
continue;
if ( player.pers["team"] == team )
{
player notify( "force_spawn" );
wait .1;
}
}
}
function updateTeamClientField()
{
ownerTeam = self gameobjects::get_owner_team();
if ( ( isdefined( self.isContested ) && self.isContested ) )
{
level clientfield::set( "hardpointteam", 3 );
}
else if ( ownerTeam == "neutral" )
{
level clientfield::set( "hardpointteam", 0 );
}
else
{
if ( ownerTeam == "allies" )
level clientfield::set( "hardpointteam", 1 );
else
level clientfield::set( "hardpointteam", 2 );
}
}
function onBeginUse( player )
{
ownerTeam = self gameobjects::get_owner_team();
if ( ownerTeam == "neutral" )
{
player thread battlechatter::gametype_specific_battle_chatter( "hq_protect", player.pers["team"] );
}
else
{
player thread battlechatter::gametype_specific_battle_chatter( "hq_attack", player.pers["team"] );
}
}
function onEndUse( team, player, success )
{
player notify( "event_ended" );
}
function onZoneCapture( player )
{
capture_team = player.pers["team"];
captureTime = getTime();
/#print( "zone captured" );#/
pause_time();
string = &"MP_KOTH_CAPTURED_BY";
level.zone.gameobject.isContested = false;
level.useStartSpawns = false;
if ( !isdefined( self.lastCaptureTeam ) || self.lastCaptureTeam != capture_team )
{
// Copy touch list so there aren't any threading issues
touchList = [];
touchKeys = GetArrayKeys( self.touchList[capture_team] );
for ( i = 0 ; i < touchKeys.size ; i++ )
touchList[touchKeys[i]] = self.touchList[capture_team][touchKeys[i]];
thread give_capture_credit( touchList, string, captureTime, capture_team, self.lastCaptureTeam );
}
level.kothCapTeam = capture_team;
self gameobjects::set_owner_team( capture_team );
if ( !level.kothMode )
self gameobjects::set_use_time( level.destroyTime );
foreach( team in level.teams )
{
if ( team == capture_team )
{
if ( !isdefined( self.lastCaptureTeam ) || ( self.lastCaptureTeam != team ) ) // If retaking this point after being contested, don't play VO again
{
globallogic_audio::leader_dialog( "kothSecured", team, undefined, "gamemode_objective", undefined, "kothActiveDialogBuffer" );
for ( index = 0; index < level.players.size; index++ )
{
player = level.players[index];
if ( player.pers["team"] == team )
{
if ( player.lastKilltime + 500 > getTime() )
{
player challenges::killedLastContester();
}
}
}
}
thread sound::play_on_players( game["objective_gained_sound"], team );
}
else
{
if ( !isdefined( self.lastCaptureTeam ) )
{
globallogic_audio::leader_dialog( "kothCaptured", team, undefined, "gamemode_objective", undefined, "kothActiveDialogBuffer" );
}
else if ( self.lastCaptureTeam == team )
{
globallogic_audio::leader_dialog( "kothLost", team, undefined, "gamemode_objective", undefined, "kothActiveDialogBuffer" );
}
thread sound::play_on_players( game["objective_lost_sound"], team );
}
}
self thread awardCapturePoints( capture_team, self.lastCaptureTeam );
self.captureCount++;
self.lastCaptureTeam = capture_team;
self gameobjects::must_maintain_claim( true );
self updateTeamClientField();
player RecordGameEvent( "hardpoint_captured" );
level notify( "zone_captured" );
level notify( "zone_captured" + capture_team );
player notify( "event_ended" );
}
function track_capture_time()
{
}
function give_capture_credit( touchList, string, captureTime, capture_team, lastCaptureTeam )
{
wait .05;
util::WaitTillSlowProcessAllowed();
players = getArrayKeys( touchList );
for ( i = 0; i < players.size; i++ )
{
player = touchList[players[i]].player;
player updateCapsPerMinute( lastCaptureTeam );
if ( !isScoreBoosting( player ) )
{
player challenges::capturedObjective( captureTime, self.trigger );
if ( level.kothStartTime + 3000 > captureTime && level.kothCapTeam == capture_team )
{
scoreevents::processScoreEvent( "quickly_secure_point", player );
}
scoreevents::processScoreEvent( "koth_secure", player );
player RecordGameEvent("capture");
level thread popups::DisplayTeamMessageToAll( string, player );
if( isdefined(player.pers["captures"]) )
{
player.pers["captures"]++;
player.captures = player.pers["captures"];
}
if ( level.kothStartTime + 500 > captureTime )
{
player challenges::immediateCapture();
}
demo::bookmark( "event", gettime(), player );
player AddPlayerStatWithGameType( "CAPTURES", 1 );
}
else
{
/#
player IPrintlnBold( "GAMETYPE DEBUG: NOT GIVING YOU CAPTURE CREDIT AS BOOSTING PREVENTION" );
#/
}
}
}
function give_held_credit( touchList, team )
{
wait .05;
util::WaitTillSlowProcessAllowed();
players = getArrayKeys( touchList );
for ( i = 0; i < players.size; i++ )
{
player = touchList[players[i]].player;
//scoreevents::processScoreEvent( "koth_held", player );
// do not know if we want the following
//player RecordGameEvent("held");
}
}
function onZoneDestroy( player )
{
destroyed_team = player.pers["team"];
/#print( "zone destroyed" );#/
scoreevents::processScoreEvent( "zone_destroyed", player );
player RecordGameEvent("destroy");
player AddPlayerStatWithGameType( "DESTRUCTIONS", 1 );
if( isdefined(player.pers["destructions"]) )
{
player.pers["destructions"]++;
player.destructions = player.pers["destructions"];
}
destroyTeamMessage = &"MP_HQ_DESTROYED_BY";
otherTeamMessage = &"MP_HQ_DESTROYED_BY_ENEMY";
if ( level.kothMode )
{
destroyTeamMessage = &"MP_KOTH_CAPTURED_BY";
otherTeamMessage = &"MP_KOTH_CAPTURED_BY_ENEMY";
}
level thread popups::DisplayTeamMessageToAll( destroyTeamMessage, player );
foreach( team in level.teams )
{
if ( team == destroyed_team )
{
globallogic_audio::leader_dialog( "koth_secured", team, undefined, "gamemode_objective" );
}
else
{
globallogic_audio::leader_dialog( "koth_destroyed", team, undefined, "gamemode_objective" );
}
}
level notify( "zone_destroyed", destroyed_team );
if ( level.kothMode )
level thread awardCapturePoints( destroyed_team );
player notify( "event_ended" );
}
function onZoneUnoccupied()
{
level notify( "zone_destroyed" );
level.kothCapTeam = "neutral";
level.zone.gameobject.wasLeftUnoccupied = true;
level.zone.gameobject.isContested = false;
level.zone.gameobject RecordGameEventNonPlayer( "hardpoint_empty" );
resume_time();
self updateTeamClientField();
}
function onZoneContested()
{
zoneOwningTeam = self gameobjects::get_owner_team();
self.wasContested = true;
self.isContested = true;
self updateTeamClientField();
self RecordGameEventNonPlayer( "hardpoint_contested" );
resume_time();
foreach( team in level.teams )
{
if ( team == zoneOwningTeam )
{
thread sound::play_on_players( game["objective_contested_sound"], team );
globallogic_audio::leader_dialog( "kothContested", team, undefined, "gamemode_objective", undefined, "kothActiveDialogBuffer" );
}
}
}
function onZoneUncontested( lastClaimTeam )
{
assert( lastClaimTeam == level.zone.gameobject gameobjects::get_owner_team() );
self.isContested = false;
pause_time();
self gameobjects::set_claim_team( lastClaimTeam );
self updateTeamClientField();
self RecordGameEventNonPlayer( "hardpoint_uncontested" );
}
function MoveZoneAfterTime( time )
{
level endon( "game_ended" );
level endon( "zone_reset" );
level.zoneMoveTime = gettime() + time * 1000;
level.zoneDestroyedByTimer = false;
wait time;
if ( !isdefined( level.zone.gameobject.wasContested ) || level.zone.gameobject.wasContested == false )
{
if ( !isdefined( level.zone.gameobject.wasLeftUnoccupied ) || level.zone.gameobject.wasLeftUnoccupied == false )
{
zoneOwningTeam = level.zone.gameobject gameobjects::get_owner_team();
challenges::controlZoneEntirely( zoneOwningTeam );
}
}
level.zoneDestroyedByTimer = true;
level.zone.gameobject RecordGameEventNonPlayer( "hardpoint_moved" );
level notify( "zone_moved" );
}
function awardCapturePoints( team, lastCaptureTeam )
{
level endon( "game_ended" );
level endon( "zone_destroyed" );
level endon( "zone_reset" );
level endon( "zone_moved" );
level notify("awardCapturePointsRunning");
level endon("awardCapturePointsRunning");
seconds = 1;
score = 1;
while ( !level.gameEnded )
{
wait seconds;
hostmigration::waitTillHostMigrationDone();
if ( !level.zone.gameobject.isContested )
{
if ( level.scorePerPlayer )
{
score = level.zone.gameobject.numTouching[team];
}
globallogic_score::giveTeamScoreForObjective( team, score );
level.kothTotalSecondsInZone++;
foreach( player in level.aliveplayers[team] )
{
if ( !IsDefined( player.touchTriggers[self.entNum] ) )
continue;
if( isdefined(player.pers["objtime"]) )
{
player.pers["objtime"]++;
player.objtime = player.pers["objtime"];
}
player AddPlayerStatWithGameType( "OBJECTIVE_TIME", 1 );
}
}
}
}
function koth_playerSpawnedCB()
{
self.lowerMessageOverride = undefined;
}
function CompareZoneIndexes( zone_a, zone_b )
{
script_index_a = zone_a.script_index;
script_index_b = zone_b.script_index;
if( !isdefined(script_index_a) && !isdefined(script_index_b) )
{
return false;
}
if( !isdefined(script_index_a) && isdefined(script_index_b) )
{
/#
println( "KOTH: Missing script_index on zone at " + zone_a.origin );
#/
return true;
}
if( isdefined(script_index_a) && !isdefined(script_index_b) )
{
/#
println( "KOTH: Missing script_index on zone at " + zone_b.origin );
#/
return false;
}
if( script_index_a > script_index_b )
{
return true;
}
return false;
}
function getZoneArray()
{
zones = getentarray( "koth_zone_center", "targetname" );
if( !isdefined( zones ) )
{
return undefined;
}
swapped = true;
n = zones.size;
while ( swapped )
{
swapped = false;
for( i = 0 ; i < n-1 ; i++ )
{
if( CompareZoneIndexes(zones[i], zones[i+1]) )
{
temp = zones[i];
zones[i] = zones[i+1];
zones[i+1] = temp;
swapped = true;
}
}
n--;
}
return zones;
}
function SetupZones()
{
maperrors = [];
zones = getZoneArray();
// if ( zones.size < 2 )
// {
// maperrors[maperrors.size] = "There are not at least 2 entities with targetname \"zone\"";
// }
trigs = getentarray("koth_zone_trigger", "targetname");
for ( i = 0; i < zones.size; i++ )
{
errored = false;
zone = zones[i];
zone.trig = undefined;
for ( j = 0; j < trigs.size; j++ )
{
if ( zone istouching( trigs[j] ) )
{
if ( isdefined( zone.trig ) )
{
maperrors[maperrors.size] = "Zone at " + zone.origin + " is touching more than one \"zonetrigger\" trigger";
errored = true;
break;
}
zone.trig = trigs[j];
break;
}
}
if ( !isdefined( zone.trig ) )
{
if ( !errored )
{
maperrors[maperrors.size] = "Zone at " + zone.origin + " is not inside any \"zonetrigger\" trigger";
continue;
}
// possible fallback (has been tested)
//zone.trig = spawn( "trigger_radius", zone.origin, 0, 128, 128 );
//errored = false;
}
assert( !errored );
zone.trigorigin = zone.trig.origin;
zone.objectiveAnchor = Spawn( "script_model", zone.origin ); // We need a script_model to attach the objective to
visuals = [];
visuals[0] = zone;
if ( isdefined( zone.target ) )
{
otherVisuals = getEntArray( zone.target, "targetname" );
for ( j = 0; j < otherVisuals.size; j++ )
{
visuals[visuals.size] = otherVisuals[j];
}
}
objective_name = istring("hardpoint");
zone.gameObject = gameobjects::create_use_object( "neutral", zone.trig, visuals, (0,0,0), objective_name );
zone.gameObject gameobjects::set_objective_entity( zone );
zone.gameObject gameobjects::disable_object();
zone.gameObject gameobjects::set_model_visibility( false );
zone.trig.useObj = zone.gameObject;
zone.trig.remote_control_player_can_trigger = true;
zone setUpNearbySpawns();
zone createZoneSpawnInfluencer();
}
if (maperrors.size > 0)
{
/#
println("^1------------ Map Errors ------------");
for(i = 0; i < maperrors.size; i++)
println(maperrors[i]);
println("^1------------------------------------");
util::error("Map errors. See above");
#/
callback::abort_level();
return;
}
level.zones = zones;
level.prevzone = undefined;
level.prevzone2 = undefined;
setupZoneExclusions();
return true;
}
function setupZoneExclusions()
{
if ( !isdefined( level.levelkothDisable ) )
return;
/* foreach( nullZone in level.levelkothDisable )
{
foreach( zone in level.zones )
{
// if ( zone.gameObject.trigger istouching( nullZone ) )
if ( zone.gameObject.trigger istouchingvolume( nullZone.origin, nullZone getmins(), nullZone getmaxs() ) )
{
if ( !isdefined( zone.gameObject.exclusions ) )
{
zone.gameObject.exclusions = [];
}
zone.gameObject.exclusions[ zone.gameObject.exclusions.size ] = nullZone;
}
}
}
*/
foreach( nullZone in level.levelkothDisable )
{
mindist = 10000000000;
foundZone = undefined;
foreach( zone in level.zones )
{
distance = DistanceSquared( nullZone.origin, zone.origin );
if ( distance < mindist )
{
foundZone = zone;
mindist = distance;
}
}
if ( isdefined( foundZone ) )
{
if ( !isdefined( foundZone.gameObject.exclusions ) )
{
foundZone.gameObject.exclusions = [];
}
foundZone.gameObject.exclusions[ foundZone.gameObject.exclusions.size ] = nullZone;
}
}
}
function setUpNearbySpawns()
{
spawns = level.spawn_all;
for ( i = 0; i < spawns.size; i++ )
{
spawns[i].distsq = distanceSquared( spawns[i].origin, self.origin );
}
// sort by distsq
for ( i = 1; i < spawns.size; i++ )
{
thespawn = spawns[i];
for ( j = i - 1; j >= 0 && thespawn.distsq < spawns[j].distsq; j-- )
spawns[j + 1] = spawns[j];
spawns[j + 1] = thespawn;
}
first = [];
second = [];
third = [];
outer = [];
thirdSize = spawns.size / 3;
for ( i = 0; i <= thirdSize; i++ )
{
first[ first.size ] = spawns[i];
}
for ( ; i < spawns.size; i++ )
{
outer[ outer.size ] = spawns[i];
if ( i <= (thirdSize*2) )
second[ second.size ] = spawns[i];
else
third[ third.size ] = spawns[i];
}
self.gameObject.nearSpawns = first;
self.gameObject.midSpawns = second;
self.gameObject.farSpawns = third;
self.gameObject.outerSpawns = outer;
}
function GetFirstZone()
{
zone = level.zones[ 0 ];
// old linear and "random" systems
level.prevzone2 = level.prevzone;
level.prevzone = zone;
level.prevZoneIndex = 0;
// new shuffled system
ShuffleZones();
ArrayRemoveValue( level.zoneSpawnQueue, zone );
return zone;
}
function GetNextZone()
{
nextZoneIndex = (level.prevZoneIndex + 1) % level.zones.size;
zone = level.zones[ nextZoneIndex ];
level.prevzone2 = level.prevzone;
level.prevzone = zone;
level.prevZoneIndex = nextZoneIndex;
return zone;
}
function PickRandomZoneToSpawn()
{
level.prevZoneIndex = randomint( level.zones.size);
zone = level.zones[ level.prevZoneIndex ];
level.prevzone2 = level.prevzone;
level.prevzone = zone;
return zone;
}
function ShuffleZones()
{
level.zoneSpawnQueue = [];
spawnQueue = ArrayCopy(level.zones);
total_left = spawnQueue.size;
while( total_left > 0 )
{
index = randomint( total_left );
valid_zones = 0;
for( zone = 0; zone < level.zones.size; zone++ )
{
if ( !isdefined(spawnQueue[zone]) )
continue;
if ( valid_zones == index )
{
// dont allow the last radio from the previous shuffle to be put first in the next
if ( level.zoneSpawnQueue.size == 0 && isdefined( level.zone ) && level.zone == spawnQueue[zone] )
continue;
level.zoneSpawnQueue[level.zoneSpawnQueue.size] = spawnQueue[zone];
spawnQueue[zone] = undefined;
break;
}
valid_zones++;
}
total_left--;
}
}
// shuffled picking
function GetNextZoneFromQueue()
{
if ( level.zoneSpawnQueue.size == 0 )
ShuffleZones();
assert( level.zoneSpawnQueue.size > 0 );
next_zone = level.zoneSpawnQueue[0];
ArrayRemoveIndex( level.zoneSpawnQueue, 0 );
return next_zone;
}
function GetCountOfTeamsWithPlayers(num)
{
has_players = 0;
foreach( team in level.teams )
{
if ( num[team] > 0 )
has_players++;
}
return has_players;
}
function GetPointCost( avgpos, origin )
{
avg_distance = 0;
total_error = 0;
distances = [];
foreach( team, position in avgpos )
{
distances[team] = Distance(origin, avgpos[team]);
avg_distance += distances[team];
}
avg_distance = avg_distance / distances.size;
foreach( team, dist in distances )
{
err = (distances[team] - avg_distance);
total_error += err * err;
}
return total_error;
}
function PickZoneToSpawn()
{
// find average of positions of each team
// (medians would be better, to get rid of outliers...)
// and find the zone which has the least difference in distance from those two averages
foreach( team in level.teams )
{
avgpos[team] = (0,0,0);
num[team] = 0;
}
for ( i = 0; i < level.players.size; i++ )
{
player = level.players[i];
if ( isalive( player ) )
{
avgpos[ player.pers["team"] ] += player.origin;
num[ player.pers["team"] ]++;
}
}
if ( GetCountOfTeamsWithPlayers(num) <= 1 )
{
zone = level.zones[ randomint( level.zones.size) ];
while ( isdefined( level.prevzone ) && zone == level.prevzone ) // so lazy
zone = level.zones[ randomint( level.zones.size) ];
level.prevzone2 = level.prevzone;
level.prevzone = zone;
return zone;
}
foreach( team in level.teams )
{
if ( num[team] == 0 )
{
avgpos[team] = undefined;
}
else
{
avgpos[team] = avgpos[team] / num[team];
}
}
bestzone = undefined;
lowestcost = undefined;
for ( i = 0; i < level.zones.size; i++ )
{
zone = level.zones[i];
// (purposefully using distance instead of distanceSquared)
cost = GetPointCost( avgpos, zone.origin );
if ( isdefined( level.prevzone ) && zone == level.prevzone )
{
continue;
}
if ( isdefined( level.prevzone2 ) && zone == level.prevzone2 )
{
if ( level.zones.size > 2 )
continue;
else
cost += 512 * 512;
}
if ( !isdefined( lowestcost ) || cost < lowestcost )
{
lowestcost = cost;
bestzone = zone;
}
}
assert( isdefined( bestzone ) );
level.prevzone2 = level.prevzone;
level.prevzone = bestzone;
return bestzone;
}
function onRoundSwitch()
{
game["switchedsides"] = !game["switchedsides"];
}
function onPlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration )
{
if ( !isPlayer( attacker ) || (level.captureTime && !self.touchTriggers.size && !attacker.touchTriggers.size) || attacker.pers["team"] == self.pers["team"] )
return;
medalGiven = false;
scoreEventProcessed = false;
ownerTeam = undefined;
if ( level.captureTime == 0 )
{
if ( !isdefined( level.zone ) )
return;
ownerTeam = level.zone.gameObject.ownerTeam ;
if ( !isdefined( ownerTeam ) || ownerTeam == "neutral" )
return;
}
if ( self.touchTriggers.size || ( level.captureTime == 0 && self IsTouching( level.zone.trig ) ) )
{
if ( level.captureTime > 0 )
{
triggerIds = getArrayKeys( self.touchTriggers );
ownerTeam = self.touchTriggers[triggerIds[0]].useObj.ownerTeam;
}
if ( ownerTeam != "neutral" )
{
attacker.lastKilltime = getTime();
team = attacker.pers["team"];
if ( team == ownerTeam )
{
if ( !medalGiven )
{
attacker medals::offenseGlobalCount();
attacker thread challenges::killedBaseOffender(level.zone.trig, weapon);
medalGiven = true;
}
//scoreevents::processScoreEvent( "killed_defender", attacker, undefined, weapon );// TFLAME 9/3/12 - Changing these events to "hardpoint_kill" as attacker / defender changes so often in Hardpoint
scoreevents::processScoreEvent( "hardpoint_kill", attacker, undefined, weapon );
self RecordKillModifier("defending");
scoreEventProcessed = true;
}
else
{
if ( !medalGiven )
{
if( isdefined(attacker.pers["defends"]) )
{
attacker.pers["defends"]++;
attacker.defends = attacker.pers["defends"];
}
attacker medals::defenseGlobalCount();
medalGiven = true;
attacker thread challenges::killedBaseDefender(level.zone.trig);
attacker RecordGameEvent("defending");
}
attacker challenges::killedZoneAttacker( weapon );
//scoreevents::processScoreEvent( "killed_attacker", attacker, undefined, weapon ); // TFLAME 9/3/12 - Changing these events to "hardpoint_kill" as attacker / defender changes so often in Hardpoint
scoreevents::processScoreEvent( "hardpoint_kill", attacker, undefined, weapon );
self RecordKillModifier("assaulting");
scoreEventProcessed = true;
}
}
}
if ( attacker.touchTriggers.size || ( level.captureTime == 0 && attacker IsTouching( level.zone.trig ) ) )
{
if ( level.captureTime > 0 )
{
triggerIds = getArrayKeys( attacker.touchTriggers );
ownerTeam = attacker.touchTriggers[triggerIds[0]].useObj.ownerTeam;
}
if ( ownerTeam != "neutral" )
{
team = self.pers["team"];
if ( team == ownerTeam )
{
if ( !medalGiven )
{
if( isdefined(attacker.pers["defends"]) )
{
attacker.pers["defends"]++;
attacker.defends = attacker.pers["defends"];
}
attacker medals::defenseGlobalCount();
medalGiven = true;
attacker thread challenges::killedBaseDefender(level.zone.trig);
attacker RecordGameEvent("defending");
}
if ( scoreEventProcessed == false )
{
attacker challenges::killedZoneAttacker( weapon );
//scoreevents::processScoreEvent( "killed_attacker", attacker, undefined, weapon );// TFLAME 9/3/12 - Changing these events to "hardpoint_kill" as attacker / defender changes so often in Hardpoint
scoreevents::processScoreEvent( "hardpoint_kill", attacker, undefined, weapon );
self RecordKillModifier("assaulting");
}
}
else
{
if ( !medalGiven )
{
attacker medals::offenseGlobalCount();
medalGiven = true;
attacker thread challenges::killedBaseOffender(level.zone.trig, weapon);
}
if ( scoreEventProcessed == false )
{
//scoreevents::processScoreEvent( "killed_defender", attacker, undefined, weapon );// TFLAME 9/3/12 - Changing these events to "hardpoint_kill" as attacker / defender changes so often in Hardpoint
scoreevents::processScoreEvent( "hardpoint_kill", attacker, undefined, weapon );
self RecordKillModifier("defending");
}
}
}
}
if ( medalGiven == true )
{
if ( ( isdefined( level.zone.gameobject.isContested ) && level.zone.gameobject.isContested ) )
{
attacker thread killWhileContesting();
}
}
}
function watchKillWhileContesting( zone_captured_team )
{
level endon( zone_captured_team );
level endon( "zone_destroyed" );
level endon( "zone_captured" );
level endon( "death" );
self util::waittill_any_return( "killWhileContesting", "disconnect" );
level notify( "abortKillWhileContesting" );
}
function killWhileContesting()
{
self notify( "killWhileContesting" );
self endon( "killWhileContesting" );
self endon( "disconnect" );
killTime = getTime();
playerteam = self.pers["team"];
if ( !isdefined ( self.clearEnemyCount ) )
{
self.clearEnemyCount = 0;
}
self.clearEnemyCount++;
zone_captured_team = "zone_captured" + playerteam;
self thread watchKillWhileContesting( zone_captured_team );
zoneReturn = level util::waittill_any_return( zone_captured_team, "zone_destroyed", "zone_captured", "death", "abortKillWhileContesting" );
if ( zoneReturn == "death" || playerteam != self.pers["team"] )
{
self.clearEnemyCount = 0;
return;
}
if ( self.clearEnemyCount >= 2 && killTime + 200 > getTime() )
{
scoreevents::processScoreEvent( "clear_2_attackers", self );
}
self.clearEnemyCount = 0;
}
function onEndGame( winningTeam )
{
for ( i = 0; i < level.zones.size; i++ )
{
level.zones[i].gameobject gameobjects::allow_use( "none" );
}
}
function createZoneSpawnInfluencer()
{
// this affects both teams
self spawning::create_influencer( "koth_large", self.gameobject.curOrigin, 0 );
self spawning::create_influencer( "koth_small", self.gameobject.curOrigin, 0 );
// turn it off for now
self spawning::enable_influencers(false);
}
function updateCapsPerMinute(lastOwnerTeam)
{
if ( !isdefined( self.capsPerMinute ) )
{
self.numCaps = 0;
self.capsPerMinute = 0;
}
// not including neutral flags as part of the boosting prevention
// to help with false positives at the start
if ( !isdefined ( lastOwnerTeam ) || lastOwnerTeam == "neutral" )
return;
self.numCaps++;
minutesPassed = globallogic_utils::getTimePassed() / ( 60 * 1000 );
// players use the actual time played
if ( IsPlayer( self ) && isdefined(self.timePlayed["total"]) )
minutesPassed = self.timePlayed["total"] / 60;
self.capsPerMinute = ( minutesPassed ? self.numCaps / minutesPassed : 0 );
if ( self.capsPerMinute > self.numCaps )
self.capsPerMinute = self.numCaps;
}
function isScoreBoosting( player )
{
if ( !level.rankedMatch )
return false;
if ( player.capsPerMinute > level.playerCaptureLPM )
return true;
return false;
}