//--- GAME RULES BEGIN ---
//Eliminate Targets in order assigned; eliminate Pursuer(s) without penalty
//Killing a Bystander who was a former Target returns him to your Target pool
//Killing 3 or more Targets in a row without dying earns a bonus
//Killing all Targets first earns a bonus
//Red = Target, Green = Bystander
//--- GAME RULES END ---

//Spec Note:  Upon entry to game - 
//  All opponents have green triangles.
//  Once you select your target, that player's triangle appears red (for you).
//  If someone who has you as a target (a Pursuer) damages you, his triangle disappears altogether (for you).
//  Once your target is eliminated, he respawns with a green triangle.
//  If your Pursuer kills you, he has a green triangle again when you respawn (unless he becomes your target). 

exec("scripts/aiBountyGame.cs");

$InvBanList[Bounty, "TurretOutdoorDeployable"] = 1;
$InvBanList[Bounty, "TurretIndoorDeployable"] = 1;
$InvBanList[Bounty, "ElfBarrelPack"] = 1;
$InvBanList[Bounty, "MortarBarrelPack"] = 1;
$InvBanList[Bounty, "PlasmaBarrelPack"] = 1;
$InvBanList[Bounty, "AABarrelPack"] = 1;
$InvBanList[Bounty, "MissileBarrelPack"] = 1;
$InvBanList[Bounty, "Mine"] = 1;

//-----------------Bounty Game score inits --------------
// .objectiveTargetKills .bystanderKills .predatorKills .suicides .deaths
function BountyGame::initGameVars(%game)
{
   %game.SCORE_PER_TARGETKILL = 1;
   %game.SCORE_PER_BYSTANDERKILL = -1;
   %game.SCORE_PER_SUICIDE = -1; 
   %game.SCORE_PER_DEATH = 0;
   %game.SCORE_PER_COMPLETION_BONUS = 5;
   %game.SIZE_STREAK_TO_AWARD = 3;  //award bonus for a winning streak this big or better
   %game.WARN_AT_NUM_OBJREM = 2; //display warning when player has only this many or less objectives left
   %game.MAX_CHEATDEATHS_ALLOWED = 1;  //number of times a player can die by cntrl-k or die by leaving mission area before being labeled a cheater

   %game.waypointFrequency = 24000;
   %game.waypointDuration = 6000;
}

function BountyGame::startMatch(%game)
{
   //call the default
   DefaultGame::startMatch(%game);

   //now give everyone who's already playing a target
   %count = ClientGroup.getCount();
   for (%i = 0; %i < %count; %i++)
   {
      %cl = ClientGroup.getObject(%i);
      %game.nextObjective(%cl);
   }
}

function BountyGame::allowsProtectedStatics(%game)
{
   return true;
}

function BountyGame::setUpTeams(%game)
{  
   if ((%group = nameToID("MissionGroup/Teams")) == -1)
     return;   

   %dropSet = new SimSet("TeamDrops0");
   MissionCleanup.add(%dropSet);

   %group.setTeam(0);

   game.numTeams = 1;  
   setSensorGroupCount(32);

   //now set up the sensor group colors - specific for bounty - everyone starts out green to everone else...
   for(%i = 0; %i < 32; %i++)
      setSensorGroupColor(%i, 0xfffffffe, "0 255 0 255");
}

function BountyGame::pickTeamSpawn(%game, %team)
{
   DefaultGame::pickTeamSpawn(%game, 0);
}

function BountyGame::claimSpawn(%game, %obj, %newTeam, %oldTeam)
{
   %newSpawnGroup = nameToId("MissionCleanup/TeamDrops0");  
   %newSpawnGroup.add(%obj);   
}

function BountyGame::clientJoinTeam( %game, %client, %team, %respawn )
{
   %game.assignClientTeam( %client );
   
   // Spawn the player:
   %game.spawnPlayer( %client, %respawn );
}
      
function BountyGame::assignClientTeam(%game, %client)
{
   %client.team = 0;

   //initialize the team array
   for (%i = 1; %i < 32; %i++)
      %game.teamArray[%i] = false;

   %count = ClientGroup.getCount();
   for (%i = 0; %i < %count; %i++)
   {
      %cl = ClientGroup.getObject(%i);
      if (%cl.team != 0)
         %game.teamArray[%cl.team] = true;
   }

   //now loop through the team array, looking for an empty team
   for (%i = 1; %i < 32; %i++)
   {
      if (! %game.teamArray[%i])
      {
         %client.team = %i;
         break;
      }
   }

   // set player's skin pref here
   setTargetSkin(%client.target, %client.skin);
   %client.justEntered = true;

   // Let everybody know you are no longer an observer:
   messageAll( 'MsgClientJoinTeam', '\c1%1 has joined the fray.', %client.name, "", %client, 1 );

   updateCanListenState( %client );

   logEcho(%client.nameBase@" (cl "@%client@") entered game");
}

function BountyGame::playerSpawned(%game, %player, %armor)
{
   DefaultGame::playerSpawned(%game, %player, %armor);
   
   %client = %player.client;
   if (%client.justEntered)
   {
      %client.justEntered = "";
      %game.updateHitLists();
   }
   %client.isPlaying = true;
   // if client spawned and has no target (e.g. when first enters game), give him one
   // if client came from observer mode, should still have a target
   if (!%client.objectiveTarget && $MatchStarted)
      %game.nextObjective(%client);

   //make sure the colors for this client are correct
   %game.updateColorMask(%client);

   //show the waypoint for this player...
   %client.damagedTargetTime = 0;
   cancel(%client.waypointSchedule);
   %game.showTargetWaypoint(%client);

   //also, anyone who has this client as an objective target, update their waypoint as well...
   for (%i = 0; %i < ClientGroup.getCount(); %i++)
   {
      %cl = ClientGroup.getObject(%i);
      if (%cl.objectiveTarget == %client)
      {
         %cl.damagedTargetTime = 0;
         cancel(%cl.waypointSchedule);
         %game.showTargetWaypoint(%cl);
      }
   }
}

function BountyGame::equip(%game, %player)
{
   for(%i =0; %i<$InventoryHudCount; %i++)
      %player.client.setInventoryHudItem($InventoryHudData[%i, itemDataName], 0, 1);

   //%player.setArmor("Light");
   %player.setInventory(EnergyPack, 1);
   %player.setInventory(RepairKit,1);
   %player.setInventory(Grenade,6);
   %player.setInventory(Blaster,1);
   %player.setInventory(Disc,1);
   %player.setInventory(Chaingun, 1);
   %player.setInventory(ChaingunAmmo, 100);
   %player.setInventory(DiscAmmo, 20);
   %player.setInventory(Beacon, 3);
   %player.setInventory(TargetingLaser, 1);
   %player.weaponCount = 3;
   
   %player.use("Blaster");
}                  

function BountyGame::updateColorMask(%game, %client)
{
   //set yourself to red, so your objectives will be red
   %redMask = 0;
   %blueMask = 0;

   %count = ClientGroup.getCount();
   for (%i = 0; %i < %count; %i++)
   {
      %cl = ClientGroup.getObject(%i);

      //first, see if the client is your target
      if (%cl == %client.objectiveTarget)
         %redMask |= (1 << %cl.team);

      //else see if the have you as a target, and have damaged you...
      else if (%cl.objectiveTarget == %client && %cl.damagedObjectiveTarget)
         %blueMask |= (1 << %cl.team);
   }

   //first everyone to green
   setSensorGroupColor(%client.team, 0xfffffffe, "0 255 0 255");

   //now set the red mask (your target)
   setSensorGroupColor(%client.team, %redMask, "255 0 0 255");

   //finally, set the blue mask (by setting alpha to 0 the damage/triangle will not appear, though
   // names still can).. if none were desired then making this group never visible would be
   // the better solution
   setSensorGroupColor(%client.team, %blueMask, "0 0 255 0");
}

function BountyGame::showTargetWaypoint(%game, %client)
{
   //AI's simply detect their target
   if (%client.isAIControlled())
   {
      if (AIClientIsAlive(%client.objectiveTarget))
         %client.clientDetected(%client.objectiveTarget);
      return;
   }

   //only show the target waypoint if the target hasn't been damaged within the frequency period
   if (getSimTime() - %client.damagedTargetTime < %game.waypointFrequency)
   {
      %client.waypointSchedule = %game.schedule(%game.waypointFrequency, "showTargetWaypoint", %client);
      return;
   }

   //flash a waypoint on the %client's current objective
   %clTarget = %client.objectiveTarget;
   if (!AIClientIsAlive(%clTarget) || !AIClientIsAlive(%client))
      return;

   //set the vis mask
   %visMask = getSensorGroupAlwaysVisMask(%clTarget.getSensorGroup());
   %visMask |= (1 << %client.getSensorGroup());
   setSensorGroupAlwaysVisMask(%clTarget.getSensorGroup(), %visMask);

   //scope the client, then set the always vis mask...
   if (isObject(%clTarget.player))
   {
      //always keep the target in scope...
      %clTarget.player.scopeToClient(%client);

      //now issue a command to kill the target
      %client.setTargetId(%clTarget.target);
      commandToClient(%client, 'TaskInfo', %client, -1, false, "Attack Target");
      %client.sendTargetTo(%client, true);

      //send the "waypoint is here sound" - QIX, need a sound!
      messageClient(%client, 'MsgBountyWaypoint', '~wfx/misc/target_waypoint.wav');
   }

   //schedule the time to hide the waypoint
   %client.waypointSchedule = %game.schedule(%game.waypointDuration, "hideTargetWaypoint", %client);
}

function BountyGame::hideTargetWaypoint(%game, %client)
{
   //AI's simply detect their target
   if (%client.isAIControlled())
   {
      if (AIClientIsAlive(%client.objectiveTarget))
         %client.clientDetected(%client.objectiveTarget);
      return;
   }

   //flash a waypoint on the %client's current objective
   %clTarget = %client.objectiveTarget;
   if (!isObject(%clTarget))
      return;

   //clear scope the client, then unset the always vis mask...
   %visMask = getSensorGroupAlwaysVisMask(%clTarget.getSensorGroup());
   %visMask &= ~(1 << %client.getSensorGroup());
   setSensorGroupAlwaysVisMask(%clTarget.getSensorGroup(), %visMask);

   //kill the actually task...
   removeClientTargetType(%client, "AssignedTask");

   //schedule the next time the waypoint should flash
   %client.waypointSchedule = %game.schedule(%game.waypointFrequency, "showTargetWaypoint", %client);
}

function BountyGame::nextObjective(%game, %client)
{
   %numValidTargets = %game.buildListValidTargets(%client);
   
   if (%numValidTargets > 0)
   {
      // if there are valid targets (other players that client hasn't killed)
      %client.objectiveTarget = %game.selectNewTarget(%client, %numValidTargets);
      %client.objectiveTargetName = detag((%client.objectiveTarget).name);
      %client.damagedObjectiveTarget = false;

      // this client has now been assigned a target
      %client.hasHadTarget = true;
      messageClient(%client, 'msgBountyTargetIs', '\c2Your target is %1.', %client.objectiveTarget.name);

      //for the client, set his mask to see his target as red, anyone who as him as a target and has damaged him is blue 
      //and everyone else is green...
      %game.updateColorMask(%client);

      //set a temporary waypoint so you can find your new target
      if (%client.isAIControlled())
         %game.aiBountyAssignTarget(%client, %client.objectiveTarget);

      //show the waypoint...
      %client.damagedTargetTime = 0;
      cancel(%client.waypointSchedule);
      %game.showTargetWaypoint(%client);
	   logEcho(%client.nameBase@" (pl "@%client.player@"/cl "@%client@") assigned objective");
   }
   else
   {
      if (%client.hasHadTarget)
      {
         // if there aren't any more valid targets and you've been assigned one,
         // that means you've killed everyone -- game over!
         %game.awardScoreCompletionBonus(%client);
         %game.gameOver();
         cycleMissions();
      }
      else
      {
         cancel(%client.awaitingTargetThread);
         %client.awaitingTargetThread = %game.schedule(500, "nextObjective", %client);  //waiting for an opponent to join, keep checking 2x per second.
      }
   }
}

function BountyGame::buildListValidTargets(%game, %cl)
{
   %availTargets = 0;
   %numClients = ClientGroup.getCount();
   for (%cIndex = 0; %cIndex < %numClients; %cIndex++)
   {
      %opponent = ClientGroup.getObject(%cIndex);
      //make sure the target isn't yourself, or an observer
      if (%opponent != %cl && %opponent.team > 0 && !%opponent.isNotInGame)
      {
         //make sure candidate for list has not already been killed by client
         if (!%cl.eliminated[%opponent])
         {
            %cl.validList[%availTargets] = %opponent;
            %availTargets++;  
         }
      }
   }

   //returns length of list (number of players eligible as targets to this client)
   %game.hudUpdateObjRem(%cl, %availTargets);
   if ((%availTargets <= %game.WARN_AT_NUM_OBJREM) && (%cl.hasHadTarget))
      %game.announceEminentWin(%cl, %availTargets);
   return %availTargets;
}

function BountyGame::selectNewTarget(%game, %cl, %numValidTargets)
{
   // pick a player at random from eligible target list
   %targetIndex = mFloor(getRandom() * (%numValidTargets - 0.01));
   //echo("picking index " @ %targetIndex @ " from ValidList");
   return %cl.validList[%targetIndex];
}

function BountyGame::updateHitLists(%game)
{
   %numClients = ClientGroup.getCount();
   for (%cIndex = 0; %cIndex < %numClients; %cIndex++)
   {
      %cl = ClientGroup.getObject(%cIndex);
      %game.buildListValidTargets(%cl);
   }
}

function BountyGame::timeLimitReached(%game)
{
   %game.gameOver();
   cycleMissions();
}

function BountyGame::gameOver(%game)
{
   //call the default
   DefaultGame::gameOver(%game);

   //send the message
   messageAll('MsgGameOver', "Match has ended.~wvoice/announcer/ann.gameover.wav" );

   messageAll('MsgClearObjHud', "");
   for(%i = 0; %i < ClientGroup.getCount(); %i++) {
      %client = ClientGroup.getObject(%i);
      %game.resetScore(%client);
      cancel(%client.waypointSchedule);
      cancel(%client.forceRespawnThread);
   }  
}

function BountyGame::clientMissionDropReady(%game, %client)
{
   %game.resetScore(%client);
   messageClient(%client, 'MsgClientReady', "", %game.class);
   messageClient(%client, 'MsgBountyTargetIs', "", "");
   messageClient(%client, 'MsgYourScoreIs', "", 0);
   //%objRem = %game.buildListValidTargets(%client);
   //messageClient(%client, 'MsgBountyObjRem', "", %objRem);
   //messageClient(%client, 'MsgYourRankIs', "", -1);

   messageClient(%client, 'MsgMissionDropInfo', '\c0You are in mission %1 (%2).', $MissionDisplayName, $MissionTypeDisplayName, $ServerName ); 

   DefaultGame::clientMissionDropReady(%game, %client);
}

function BountyGame::AIHasJoined(%game, %client)
{
   //let everyone know the player has joined the game
   //messageAllExcept(%client, -1, 'MsgClientJoinTeam', '%1 has joined the fray.', %client.name, "", %client, 1);
}

function BountyGame::forceRespawn(%game, %client)
{
   //make sure the player hasn't already respawned
   if (isObject(%client.player))
      return;

	commandToClient(%client, 'setHudMode', 'Standard');
   Game.spawnPlayer( %client, true );
   %client.camera.setFlyMode();
   %client.setControlObject(%client.player);
}

function BountyGame::onClientKilled(%game, %clVictim, %clKiller, %damageType, %implement, %damageLoc)
{
   DefaultGame::onClientKilled(%game, %clVictim, %clKiller, %damageType, %implement, %damageLoc);

   // you had your shot
   %clVictim.isPlaying = false;
   cancel(%clVictim.awaitingTargetThread);   

   // any time a person dies, the kill streak is reset
   %clVictim.killStreak = 0;

   //force the player to respawn
   if (!%clVictim.isAIControlled())
      %clVictim.forceRespawnThread = %game.schedule(5000, forceRespawn, %clVictim);
}

function BountyGame::onClientLeaveGame(%game, %clientId)
{
   DefaultGame::onClientLeaveGame(%game, %clientId);
   %clientId.isNotInGame = true;

   %numClients = ClientGroup.getCount();
   for (%index = 0; %index < %numClients; %index++)
   {
      %possPredator = ClientGroup.getObject(%index);
      // make sure this guy gets erased from everyone's eliminated list
      %possPredator.eliminated[%clientId] = "";

      //no need for this - anyone who has him as a target will have their colors reset in game.nextObjective()
      //reset everyones triangles to friendly and visible so the next guy that joins, doesn't get the old settings
      //setTargetFriendlyMask(%possPredator.target, getTargetFriendlyMask(%possPredator.target) | (1<<getTargetSensorGroup(%clientID.target)));
      //setTargetNeverVisMask(%possPredator.target, getTargetNeverVisMask(%possPredator.target) & ~(1<<getTargetSensorGroup(%clientID.target)));

      // if anyone had this client as a target, get them a new target
      if (%possPredator.objectiveTarget == %clientID)
      {
         messageClient(%possPredator, 'msgBountyTargetDropped', '\c2%1 has left the game and is no longer a target', %clientId.name); 
         %game.nextObjective(%possPredator);
      }
   }  
}

function BountyGame::onClientEnterObserverMode(%game, %clientId)
{
   //cancel the respawn schedule
   cancel(%clientId.forceRespawnThread);

   //notify everyone else, and choose a new objective if required...
   %numClients = ClientGroup.getCount();
   for (%index = 0; %index < %numClients; %index++)
   {
      // if anyone had this guy as a target, pick new target
      %possPredator = ClientGroup.getObject(%index);
      if (%possPredator.objectiveTarget == %clientId)
      {
         messageClient(%possPredator, 'msgBountyTargetEntObs', '\c2%1 has left the playfield and is no longer a target', %clientId.name);
         %game.nextObjective(%possPredator);
      }
   }
}

function BountyGame::onClientDamaged(%game, %clVictim, %clAttacker, %damageType, %sourceObject)
{ 
   DefaultGame::onClientDamaged(%game, %clVictim, %clAttacker, %damageType, %sourceObject);
   
   //the updateColorMask function chooses red over blue, so no need to check if each is the other's target
   if (%clAttacker.objectiveTarget == %clVictim && !%clAttacker.damagedObjectiveTarget)
   {
      %clAttacker.damagedObjectiveTarget = true;
      %game.updateColorMask(%clVictim);
   }

   //update the time at which the attacker damaged his target
   if (%clAttacker.objectiveTarget == %clVictim)
      %clAttacker.damagedTargetTime = getSimTime();
}

function BountyGame::updateKillScores(%game, %clVictim, %clKiller, %damageType, %implement)
{
   
   if (%game.testSuicide(%clVictim, %clKiller, %damageType))
      %game.awardScoreSuicide(%clVictim, %damageType);     
   else if (%game.testTargetKill(%clVictim, %clKiller)) 
      %game.awardScoreTargetKill(%clVictim, %clKiller);
   else if (%game.testPredatorKill(%clVictim, %clKiller))
      %game.awardScorePredatorKill(%clVictim, %clKiller);
   else  
      %game.awardScoreBystanderKill(%clVictim, %clKiller);
      
   if (%game.testCheating(%clVictim))
      %game.announceCheater(%clVictim);       
   
   %game.recalcScore(%clKiller);
   %game.recalcScore(%clVictim);          
}

function BountyGame::testCheating(%game, %client)
{
   return (%client.cheated > %game.MAX_CHEATDEATHS_ALLOWED);   
}  

function BountyGame::testSuicide(%game, %victimID, %killerID, %damageType)
{     
   return ((%victimID == %killerID) || (%damageType == $DamageType::Ground) || (%damageType == $DamageType::Suicide) || (%damageType == $DamageType::OutOfBounds));      
}

function BountyGame::testTargetKill(%game, %clVictim, %clKiller)
{
   // was the person you just killed your target?
   return (%clKiller.objectiveTarget == %clVictim);   
}  

function BountyGame::testPredatorKill(%game, %clVictim, %clKiller)
{
   // were you the target of the person you just killed?
   return (%clVictim.objectiveTarget == %clKiller);   
}  

function BountyGame::awardScoreSuicide(%game, %clVictim, %damageType)
{
   //now loop through the client, and award the kill to any attacker who damaged the client within 20 secs...
   for (%i = 0; %i < ClientGroup.getCount(); %i++)
   {
      %cl = ClientGroup.getObject(%i);
      if (%cl.objectiveTarget == %clVictim)
      {
         %game.awardScoreTargetKill(%clVictim, %cl, true);
         messageClient(%clVictim, 'msgBountyTarKil', '\c2You eliminated yourself.  %1 has been awarded the bounty!', %cl.name);
      }
   }

   //update the "cheated" count
   if (%damageType == $DamageType::Suicide)
   {
      %clVictim.cheated++;
      DefaultGame::awardScoreSuicide(%game, %clVictim);
   }

   %game.recalcScore(%clVictim); 
   %clVictim.killStreak = 0;
}

function BountyGame::awardScoreTargetKill(%game,%clVictim, %clKiller, %victimSuicided)
{
   // congratulations, you killed your target
   %clKiller.objectiveTargetKills++;
   %clKiller.kills = %clKiller.objectiveTargetKills;   
   %clKiller.eliminated[%clVictim] = true;

   if (%victimSuicided)
   {
      if (%clVictim.gender $= "Female")
         messageClient(%clKiller, 'msgBountyTarKil', '\c2Target %1 helped you out by eliminating herself!', %clVictim.name);
      else
         messageClient(%clKiller, 'msgBountyTarKil', '\c2Target %1 helped you out by eliminating himself!', %clVictim.name);
   }
   else
      messageClient(%clKiller, 'msgBountyTarKil', '\c2You eliminated target %1!', %clVictim.name);
   
   if (%game.SCORE_PER_TARGETKILL != 0)
      %game.recalcScore(%clKiller);
   
   %clKiller.killStreak++;
   %clVictim.killStreak = 0;
   if (%clKiller.killStreak >= %game.SIZE_STREAK_TO_AWARD) //award points for a kill streak
      %game.awardScoreKillStreak(%clKiller);

   //the victim is no longer the objective of the killer, reset his colors.
   //The colors for the killer will be reset in game.nextObjective()
   %clKiller.objectiveTarget = "";
   %game.updateColorMask(%clVictim);
   
   %game.nextObjective(%clKiller);

   //since the killer scored, update everyone who's got him as a target...
   for (%i = 0; %i < ClientGroup.getCount(); %i++)
   {
      %cl = ClientGroup.getObject(%i);
      if (%cl.objectiveTarget == %clKiller)
      {
         %cl.damagedTargetTime = 0;
         cancel(%cl.waypointSchedule);
         %game.showTargetWaypoint(%cl);
      }
   }
}  

function BountyGame::awardScoreBystanderKill(%game, %clVictim, %clKiller)
{
   // uh oh, you killed someone other than your target or the person targeting you
   %clKiller.bystanderKills++;
   //%clVictim.killStreak = 0;  //don't penalize the bystander right now, maybe change this later
   // if you'd already killed him legally, he's back on your prospective target list    

   if (mAbs(%game.SCORE_PER_BYSTANDERKILL) > 1)
     %plural = (%game.SCORE_PER_BYSTANDERKILL > 1 ? "s" : "");   
   
   if (%game.SCORE_PER_BYSTANDERKILL != 0)
   {
      messageClient(%clKiller, 'msgBountyBysKil', '\c0You have been penalized %1 point%2 for killing bystander %3.', mAbs(%game.SCORE_PER_BYSTANDERKILL), %plural, %clVictim.name); 
      messageClient(%clVictim, 'msgBountyBystander', '\c2You were the victim of %1\'s lousy aim!  He was penalized.', %clKiller.name);
      %game.recalcScore(%clKiller);
   }

   //add a target back to your list (any target)
   %targetCount = 0;
   for (%i = 0; %i < ClientGroup.getCount(); %i++)
   {
      %cl = ClientGroup.getObject(%i);
      if (%clKiller.eliminated[%cl])
      {
         %addTargetsArray[%targetCount] = %cl;
         %targetCount++;
      }
   }

   //see if we found any targets we can add back on...
   if (%targetCount > 0)
   {
      %clKiller.eliminated[%addTargetsArray[mFloor(getRandom() * (%targetCount - 0.01))]] = false;
      messageClient(%clKiller, 'msgBountyBysRedo', '\c2One target has been added back onto your Bounty list.');
   }

   %clKiller.killStreak = 0;
}

function BountyGame::awardScorePredatorKill(%game, %clVictim, %clKiller)
{   
   %clVictim.killStreak = 0;
   messageClient(%clKiller, 'msgBountyPredKill', '\c0You have temporarily fended off %1.', %clVictim.name);

   //now, try to assign a new objective target for clVictim...
   %game.nextObjective(%clVictim);

   //also update the color mask for the killer...
   %game.updateColorMask(%clKiller);
}  

function BountyGame::awardScoreKillStreak(%game, %cl)
{
   %bonus = (%cl.killStreak - %game.SIZE_STREAK_TO_AWARD) + 1;
   %cl.streakPoints += %bonus;

   messageClient(%cl,'msgBountyPlrStrkBonus', '\c0You received a %1 point bonus for a %2 kill streak!', %bonus, %cl.killStreak);
   messageAll('msgBountyStreakBonus', '\c0%1 has eliminated %2 targets in a row!', %cl.name, %cl.killStreak, %bonus); //send bonus for sound parsing     
    //callback exists in client.cs for 'msgBountyStreakBonus', to play repeating bell sound length dependent on streak
}

function BountyGame::awardScoreCompletionBonus(%game, %cl)
{
   // you killed everybody who was on your list
   %cl.compBonus = 1;
   if (%game.SCORE_PER_COMPLETION_BONUS != 0)   
      messageAll('msgBountyCompBonus', '\c2%1 receives a %2 point bonus for completing all objectives.', %cl.name, %game.SCORE_PER_COMPLETION_BONUS); 

   %game.recalcScore(%cl); 
}  


function BountyGame::announceEminentWin(%game, %cl, %objRem)
{
     %wav = "~wfx/misc/bounty_objRem" @ %objRem @ ".wav";
     %plural = (%objRem > 1 ? "s" : "");
     if (%objRem > 0)
      messageAll('msgBountyEminent', '\c2%1 has only %2 target%3 left!%4', %cl.name, %objRem, %plural, %wav);
     else
      messageAll('msgBountyOver', '\c2%1 has eliminated all targets!~wfx/misc/bounty_completed.wav', %cl.name);
}

function BountyGame::announceCheater(%game, %client)
{
   if (%client.cheated > 1)
      %plural = "s";
   else
      %plural = "";
   messageAll('msgCheater', '%1 has suicided \c2%2 \c0time%3!', %client.name, %client.cheated, %plural);
}  

function BountyGame::recalcScore(%game, %cl)
{
   %cl.score = %cl.objectiveTargetKills * %game.SCORE_PER_TARGETKILL;
   %cl.score += %cl.bystanderKills * %game.SCORE_PER_BYSTANDERKILL;
   %cl.score += %cl.suicides * %game.SCORE_PER_SUICIDE;
   %cl.score += %cl.compBonus * %game.SCORE_PER_COMPLETION_BONUS;
   %cl.score += %cl.streakPoints;  //this awarded in awardScoreKillStreak

   messageClient(%cl, 'MsgYourScoreIs', "", %cl.score);

   %game.recalcTeamRanks(%cl);      
}  

function BountyGame::resetScore(%game, %cl)
{
   %cl.score = 0;
   %cl.kills = 0;
   %cl.objectiveTargetKills = 0;
   %cl.bystanderKills = 0;
   %cl.suicides = 0;
   %cl.compBonus = 0;
   %cl.streakPoints = 0;

   cancel(%cl.awaitingTargetThread);

   %cl.killstreak = "";
   %cl.cheated = "";
   %cl.hasHadTarget = false;

   //reset %cl's hit list
   //note that although this only clears clients (enemies) currently in the game, 
   //clients (enemies) who left early should have been cleared at that time.
   %numClients = ClientGroup.getCount();
   for (%count = 0; %count < %numClients; %count++)
   {
      %enemy = ClientGroup.getObject(%count);
      %cl.eliminated[%enemy] = "";
   }  
}

function BountyGame::hudUpdateObjRem(%game, %client, %availTargets)
{
   // how many people do you have left to kill?     
    messageClient(%client, 'msgBountyObjRem', "", %availTargets);
}  

function BountyGame::enterMissionArea(%game, %playerData, %player)
{
   %player.client.outOfBounds = false; 
   messageClient(%player.client, 'EnterMissionArea', '\c1You are back in the mission area.');
   cancel(%player.alertThread);
	logEcho(%player.client.nameBase@" (pl "@%player@"/cl "@%player.client@") entered mission area");
}

function BountyGame::leaveMissionArea(%game, %playerData, %player)
{
   if(%player.getState() $= "Dead")
      return;

   %player.client.outOfBounds = true;
   messageClient(%player.client, 'LeaveMissionArea', '\c1You have left the mission area. Return or take damage.~wfx/misc/warning_beep.wav');
   %player.alertThread = %game.schedule(1000, "AlertPlayer", 3, %player);
	logEcho(%player.client.nameBase@" (pl "@%player@"/cl "@%player.client@") left mission area");
}

function BountyGame::AlertPlayer(%game, %count, %player)
{
   if(%count > 1)
      %player.alertThread = %game.schedule(1000, "AlertPlayer", %count - 1, %player);
   else 
      %player.alertThread = %game.schedule(1000, "MissionAreaDamage", %player);
}

function BountyGame::MissionAreaDamage(%game, %player)
{
   if(%player.getState() !$= "Dead") {                                   
      %player.setDamageFlash(0.1);
      %prevHurt = %player.getDamageLevel();
      %player.setDamageLevel(%prevHurt + 0.05);
      // a little redundancy to see if the lastest damage killed the player
      if(%player.getState() $= "Dead")
         %game.onClientKilled(%player.client, 0, $DamageType::OutOfBounds);
      else
         %player.alertThread = %game.schedule(1000, "MissionAreaDamage", %player);
   }
   else  
   {
      %game.onClientKilled(%player.client, 0, $DamageType::OutOfBounds);
   }  
}


function BountyGame::updateScoreHud(%game, %client, %tag)
{
   // clear the header:
   messageClient( %client, 'SetScoreHudHeader', "", "" );

   // send the subheader:
   messageClient( %client, 'SetScoreHudSubheader', "", '<tab:15,235,335>\tPLAYER\tSCORE\tTARGETS LEFT' );

   for ( %index = 0; %index < $TeamRank[0, count]; %index++ )
   {
      //get the client info
      %cl = $TeamRank[0, %index];
      %clScore = %cl.score $= "" ? 0 : %cl.score;

      //find out how many targets this client has left
      %clTargets = 0;
      for (%cIndex = 0; %cIndex < ClientGroup.getCount(); %cIndex++)
      {
         %opponent = ClientGroup.getObject(%cIndex);
         if (!%opponent.isNotInGame && %opponent.team > 0 && %opponent != %cl && !%cl.eliminated[%opponent])
            %clTargets++;
      }

      //find out if we've killed this target
      %clKilled = "";
      if ( %client.eliminated[%cl] )
         %clKilled = "<font:univers condensed:18>ELIMINATED";

      %clStyle = %cl == %client ? "<color:dcdcdc>" : "";
 
      //if the client is not an observer, send the message
      if (%client.team != 0)
      {
         messageClient( %client, 'SetLineHud', "", %tag, %index, '%5<tab:20,430>\t<clip:200>%1</clip><rmargin:280><just:right>%2<rmargin:400><just:right>%3<rmargin:610><just:left>\t<color:FF0000>%4', 
               %cl.name, %clScore, %clTargets, %clKilled, %clStyle );
      }
      //else for observers, create an anchor around the player name so they can be observed
      else
      {
         messageClient( %client, 'SetLineHud', "", %tag, %index, '%5<tab:20,430>\t<clip:200><a:gamelink\t%6>%1</a></clip><rmargin:280><just:right>%2<rmargin:400><just:right>%3<rmargin:610><just:left>\t<color:FF0000>%4', 
               %cl.name, %clScore, %clTargets, %clKilled, %clStyle, %cl );
      }
   }

   // Tack on the list of observers:
   %observerCount = 0;
   for (%i = 0; %i < ClientGroup.getCount(); %i++)
   {
      %cl = ClientGroup.getObject(%i);
      if (%cl.team == 0)
         %observerCount++;
   }

   if (%observerCount > 0)
   {
	   messageClient( %client, 'SetLineHud', "", %tag, %index, "");
      %index++;
		messageClient(%client, 'SetLineHud', "", %tag, %index, '<tab:10, 310><spush><font:Univers Condensed:22>\tOBSERVERS (%1)<rmargin:260><just:right>TIME<spop>', %observerCount);
      %index++;
      for (%i = 0; %i < ClientGroup.getCount(); %i++)
      {
         %cl = ClientGroup.getObject(%i);
         //if this is an observer
         if (%cl.team == 0)
         {
            %obsTime = getSimTime() - %cl.observerStartTime;
            %obsTimeStr = %game.formatTime(%obsTime, false);
		      messageClient( %client, 'SetLineHud', "", %tag, %index, '<tab:20, 310>\t<clip:150>%1</clip><rmargin:260><just:right>%2',
		                     %cl.name, %obsTimeStr );
            %index++;
         }
      }
   }

   //clear the rest of Hud so we don't get old lines hanging around...
   messageClient(%client, 'ClearHud', "", %tag, %index);
}

function BountyGame::applyConcussion(%game, %player)
{
}
