//--- 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;
    //award bonus for a winning streak this big or better
    %game.SIZE_STREAK_TO_AWARD = 3;
    //display warning when player has only this many or less objectives left
    %game.WARN_AT_NUM_OBJREM = 2;
    //number of times a player can die by ctrl-k or die by leaving mission area
    //before being labeled a cheater
    %game.MAX_CHEATDEATHS_ALLOWED = 1;

    %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 everyone 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);
}

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.client.clearBackpackIcon();

    //%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);
    }
    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);
            //waiting for an opponent to join, keep checking 2x per second.
            %client.awaitingTargetThread = %game.schedule(
                500, "nextObjective", %client);
        }
    }
}

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));
    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;
    //award points for a kill streak
    if (%clKiller.killStreak >= %game.SIZE_STREAK_TO_AWARD)
        %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);
}

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);
}

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)
{
}
