Fix TOC reports/weapons secured info

Fix TOC reports/weapons secured info

class Response extends SwatGame.SwatMutator;

/**
 * Copyright (c) 2014 Sergei Khoroshilov <kh.sergei@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * This class handles response objects that expose simple public API:
 *
 *     AddInfo() - populates object with server information
 *     AddPlayers() - populates object with player details
 *     Read() - read a piece of response formed off previously populated data
 *     IsEmpty() - tell whether response object is out of data
 *
 * Every single packet read with a Read() call contains
 * a \queryid\%N% fragment with a 1-based number indicating current packet count.
 * The last response packet is also provided with a \final\ fragment.
 *
 * Read more about Gamespy protocol at <http://int64.org/docs/gamestat-protocols/gamespy.html>
 */

/**
 * Although the most reliable udp packet size is believed to be 508,
 * the final packet does also contain the extra \final\ string
 * @type int
 */
const MAX_PACKET_SIZE = 500;

/**
 * Array of fragments awaiting of being split and read chunk by chunk
 * @type array<string>
 */
var protected array<string> Fragments;

/**
 * Current packet count
 * @type int
 */
var protected int PacketCount;

/**
 * Indicate whether the response instance is occupied by listener
 * @type bool
 */
var protected bool bOccupied;

/**
 * Tell whether the fragments array is out of elements
 *
 * @return  bool
 */
public function bool IsEmpty()
{
    return self.Fragments.Length == 0;
}

/**
 * Return the next piece of response text
 *
 * @return  string
 */
public function string Read()
{
    local int FragmentCount;
    local array<string> Packet;

    if (self.IsEmpty())
    {
        return "";
    }
    // Prepopulate the array with packet information fragments
    Packet[Packet.Length] = "queryid";
    Packet[Packet.Length] = string(++self.PacketCount);
    // Only pack new fragments if they dont break the limit
    while (true)
    {
        // Stop when exhausted
        if (self.IsEmpty())
        {
            break;
        }
        // Dont let the next pair of fragments to pass if the two break the limit
        if (++FragmentCount % 2 != 0)
        {
            if (self.GetPacketSize(Packet) + (Len(self.Fragments[0]) + Len(self.Fragments[1]) + 2) > class'Response'.const.MAX_PACKET_SIZE)
            {
                break;
            }
        }
        Packet[Packet.Length] = self.Fragments[0];
        // Keep removing acquired fragments untill the array is empty..
        self.Fragments.Remove(0, 1);
    }
    // Rearrange packet array elements so the queryid token
    // and it's value are at the end of the packet
    Packet[Packet.Length] = Packet[0];
    Packet[Packet.Length] = Packet[1];
    // Shift the original queryid elements
    Packet.Remove(0, 2);
    // Add final token in case the array is out of fragments
    if (self.IsEmpty())
    {
        Packet[Packet.Length] = "final";
        Packet[Packet.Length] = "";;
    }
    // Split packets always begin with a backslash
    return "\\" $ class'Utils.ArrayUtils'.static.Join(Packet, "\\");;
}

/**
 * Extend response with server details
 *
 * @return  void
 */
public function AddInfo()
{
    self.AddPair("hostname", ServerSettings(Level.CurrentServerSettings).ServerName);
    self.AddPair("numplayers", SwatGameInfo(Level.Game).NumberOfPlayersForServerBrowser());
    self.AddPair("maxplayers", ServerSettings(Level.CurrentServerSettings).MaxPlayers);
    self.AddPair("gametype", SwatGameInfo(Level.Game).GetGameModeName());
    self.AddPair("gamevariant", Level.ModName);
    self.AddPair("mapname", Level.Title);
    self.AddPair("hostport", SwatGameInfo(Level.Game).GetServerPort());
    self.AddPair("password", Lower(string(Level.Game.GameIsPasswordProtected())));
    self.AddPair("gamever", Level.BuildVersion);
    self.AddPair("round", ServerSettings(Level.CurrentServerSettings).RoundNumber+1);
    self.AddPair("numrounds", ServerSettings(Level.CurrentServerSettings).NumRounds);
    self.AddPair("timeleft", SwatGameReplicationInfo(Level.Game.GameReplicationInfo).RoundTime);

    switch (ServerSettings(Level.CurrentServerSettings).GameType)
    {
        #if IG_SPEECH_RECOGNITION
        case MPM_COOPQMM:
        #endif
        case MPM_COOP:
        case MPM_RapidDeployment:
        case MPM_VIPEscort:
            self.AddPair("timespecial", SwatGameReplicationInfo(Level.Game.GameReplicationInfo).SpecialTime);
            break;
    }

    switch (ServerSettings(Level.CurrentServerSettings).GameType)
    {
        #if IG_SPEECH_RECOGNITION
        case MPM_COOPQMM:
        #endif
        case MPM_COOP:
            self.AddCOOPInfo();
            break;
        case MPM_RapidDeployment:
            self.AddPair("bombsdefused", SwatGameReplicationInfo(Level.Game.GameReplicationInfo).DiffusedBombs);
            self.AddPair("bombstotal", SwatGameReplicationInfo(Level.Game.GameReplicationInfo).TotalNumberOfBombs);
            // no break
        default:
            self.AddPair("swatscore", SwatGameInfo(Level.Game).GetTeamFromID(0).NetScoreInfo.GetScore());
            self.AddPair("suspectsscore", SwatGameInfo(Level.Game).GetTeamFromID(1).NetScoreInfo.GetScore());
            self.AddPair("swatwon", SwatGameInfo(Level.Game).GetTeamFromID(0).NetScoreInfo.GetRoundsWon());
            self.AddPair("suspectswon", SwatGameInfo(Level.Game).GetTeamFromID(1).NetScoreInfo.GetRoundsWon());
            break;
    }
}

/**
 * Add
 *
 * @return  void
 */
public function AddCOOPInfo()
{
    local int i;
    local MissionObjectives Objectives;
    local Procedures Procedures;

    Objectives = SwatRepo(Level.GetRepo()).MissionObjectives;

    // "obj_"Objective name => Objective status pair
    for (i = 0; i < Objectives.Objectives.Length; i++)
    {
        // Skip the hidden objective
        if (Objectives.Objectives[i].name == 'Automatic_DoNot_Die')
        {
            continue;
        }
        self.AddPair("obj_" $ Objectives.Objectives[i].name, SwatGameReplicationInfo(Level.Game.GameReplicationInfo).ObjectiveStatus[i]);
    }

    // Add the params "tocreports" and "weaponssecured"
    Procedures = SwatRepo(Level.GetRepo()).Procedures;

    for (i = 0; i < Procedures.Procedures.Length; i++)
    {
        switch (Procedures.Procedures[i].class.name)
        {
            case 'Procedure_ReportCharactersToTOC':
                self.AddPair("tocreports", Procedures.Procedures[i].Status());
                break;
            case 'Procedure_SecureAllWeapons':
                self.AddPair("weaponssecured", Procedures.Procedures[i].Status());
                break;
        }
    }
}

/**
 * Append player details to response
 *
 * @return  void
 */
public function AddPlayers()
{
    local int i;
    local PlayerController PC;

    foreach DynamicActors(class'PlayerController', PC)
    {
        self.AddPair("player_" $ i, PC.PlayerReplicationInfo.PlayerName);
        self.AddPair("score_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetScore());
        self.AddPair("ping_" $ i, Min(999, PC.PlayerReplicationInfo.Ping));
        self.AddPair("team_" $ i, NetTeam(PC.PlayerReplicationInfo.Team).GetTeamNumber());

        // COOP status
        if (Level.IsCOOPServer)
        {
            self.AddPair("coopstatus_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).COOPPlayerStatus);
        }

        // Kills
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetEnemyKills() > 0)
        {
            self.AddPair("kills_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetEnemyKills());
        }

        // Deaths
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetTimesDied() > 0)
        {
            self.AddPair("deaths_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetTimesDied());
        }

        // Team Kills
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetFriendlyKills() > 0)
        {
            self.AddPair("tkills_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetFriendlyKills());
        }

        // Arrests
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetArrests() > 0)
        {
            self.AddPair("arrests_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetArrests());
        }

        // Arrested
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetTimesArrested() > 0)
        {
            self.AddPair("arrested_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetTimesArrested());
        }

        // The VIP
        if (SwatGamePlayerController(PC).ThisPlayerIsTheVIP)
        {
            self.AddPair("vip_" $ i, 1);
        }

        // VIP Escapes
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetVIPPlayerEscaped() > 0)
        {
            self.AddPair("vescaped_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetVIPPlayerEscaped());
        }

        // VIP Captures
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetArrestedVIP() > 0)
        {
            self.AddPair("arrestedvip_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetArrestedVIP());
        }

        // VIP Rescues
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetUnarrestedVIP() > 0)
        {
            self.AddPair("unarrestedvip_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetUnarrestedVIP());
        }

        // VIP Kills Valid
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetKilledVIPValid() > 0)
        {
            self.AddPair("validvipkills_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetKilledVIPValid());
        }

        // VIP Kills Invalid
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetKilledVIPInvalid() > 0)
        {
            self.AddPair("invalidvipkills_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetKilledVIPInvalid());
        }

        // Bombs Disarmed
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetBombsDiffused() > 0)
        {
            self.AddPair("bombsdiffused_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetBombsDiffused());
        }

        // RD Crybaby
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetRDCrybaby() > 0)
        {
            self.AddPair("rdcrybaby_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetRDCrybaby());
        }

        #if IG_SPEECH_RECOGNITION
        // SG Crybaby
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetSGCrybaby() > 0)
        {
            self.AddPair("sgcrybaby_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetSGCrybaby());
        }

        // Case Escapes
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetEscapedSG() > 0)
        {
            self.AddPair("escapedcase_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetEscapedSG());
        }

        // Case Kills
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetKilledSG() > 0)
        {
            self.AddPair("killedcase_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetKilledSG());
        }
        #endif

        i++;
    }
}

/**
 * Add a new key-value pair
 *
 * @param   string Key
 * @param   string Value
 * @return  void
 */
public function AddPair(coerce string Key, coerce string Value)
{
    self.AddFragment(Key);
    self.AddFragment(Value);
}

/**
 * Append a new fragment to the list of fragments
 *
 * @param   string Fragment
 * @return  void
 */
protected function AddFragment(coerce string Fragment)
{
    // Trim long fragments
    self.Fragments[self.Fragments.Length] = Left(Fragment, class'Response'.const.MAX_PACKET_SIZE/3);
}

/**
 * Compute and return the length of given packet fragments
 * as if they were formed up into a packet using a backslash as the delimiter
 *
 * @param   array<string> Packet
 * @return  int
 */
static function int GetPacketSize(array<string> Packet)
{
    local int i, Size;

    for (i = 0; i < Packet.Length; i++)
    {
        Size += Len(Packet[i]);
    }
    return Size + Packet.Length;
}

/**
 * Tell whether the response instance has been occupied
 *
 * @return  bool
 */
public function bool IsOccupied()
{
    return self.bOccupied;
}

/**
 * Occupy the instance
 *
 * @return  void
 */
public function Occupy()
{
    self.bOccupied = true;
}

/**
 * Free the instance
 *
 * @return  void
 */
public function Free()
{
    self.Fragments.Remove(0, self.Fragments.Length);
    self.PacketCount = 0;
    self.bOccupied = false;
}

event Destroyed()
{
    self.Free();
    Super.Destroyed();
}

/* vim: set ft=java: */

class Response extends SwatGame.SwatMutator;

/**
 * Copyright (c) 2014 Sergei Khoroshilov <kh.sergei@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * This class handles response objects that expose simple public API:
 *
 *     AddInfo() - populates object with server information
 *     AddPlayers() - populates object with player details
 *     Read() - read a piece of response formed off previously populated data
 *     IsEmpty() - tell whether response object is out of data
 *
 * Every single packet read with a Read() call contains
 * a \queryid\%N% fragment with a 1-based number indicating current packet count.
 * The last response packet is also provided with a \final\ fragment.
 *
 * Read more about Gamespy protocol at <http://int64.org/docs/gamestat-protocols/gamespy.html>
 */

/**
 * Although the most reliable udp packet size is believed to be 508,
 * the final packet does also contain the extra \final\ string
 * @type int
 */
const MAX_PACKET_SIZE = 500;

/**
 * Array of fragments awaiting of being split and read chunk by chunk
 * @type array<string>
 */
var protected array<string> Fragments;

/**
 * Current packet count
 * @type int
 */
var protected int PacketCount;

/**
 * Indicate whether the response instance is occupied by listener
 * @type bool
 */
var protected bool bOccupied;

/**
 * Tell whether the fragments array is out of elements
 *
 * @return  bool
 */
public function bool IsEmpty()
{
    return self.Fragments.Length == 0;
}

/**
 * Return the next piece of response text
 *
 * @return  string
 */
public function string Read()
{
    local int FragmentCount;
    local array<string> Packet;

    if (self.IsEmpty())
    {
        return "";
    }
    // Prepopulate the array with packet information fragments
    Packet[Packet.Length] = "queryid";
    Packet[Packet.Length] = string(++self.PacketCount);
    // Only pack new fragments if they dont break the limit
    while (true)
    {
        // Stop when exhausted
        if (self.IsEmpty())
        {
            break;
        }
        // Dont let the next pair of fragments to pass if the two break the limit
        if (++FragmentCount % 2 != 0)
        {
            if (self.GetPacketSize(Packet) + (Len(self.Fragments[0]) + Len(self.Fragments[1]) + 2) > class'Response'.const.MAX_PACKET_SIZE)
            {
                break;
            }
        }
        Packet[Packet.Length] = self.Fragments[0];
        // Keep removing acquired fragments untill the array is empty..
        self.Fragments.Remove(0, 1);
    }
    // Rearrange packet array elements so the queryid token
    // and it's value are at the end of the packet
    Packet[Packet.Length] = Packet[0];
    Packet[Packet.Length] = Packet[1];
    // Shift the original queryid elements
    Packet.Remove(0, 2);
    // Add final token in case the array is out of fragments
    if (self.IsEmpty())
    {
        Packet[Packet.Length] = "final";
        Packet[Packet.Length] = "";;
    }
    // Split packets always begin with a backslash
    return "\\" $ class'Utils.ArrayUtils'.static.Join(Packet, "\\");;
}

/**
 * Extend response with server details
 *
 * @return  void
 */
public function AddInfo()
{
    self.AddPair("hostname", ServerSettings(Level.CurrentServerSettings).ServerName);
    self.AddPair("numplayers", SwatGameInfo(Level.Game).NumberOfPlayersForServerBrowser());
    self.AddPair("maxplayers", ServerSettings(Level.CurrentServerSettings).MaxPlayers);
    self.AddPair("gametype", SwatGameInfo(Level.Game).GetGameModeName());
    self.AddPair("gamevariant", Level.ModName);
    self.AddPair("mapname", Level.Title);
    self.AddPair("hostport", SwatGameInfo(Level.Game).GetServerPort());
    self.AddPair("password", Lower(string(Level.Game.GameIsPasswordProtected())));
    self.AddPair("gamever", Level.BuildVersion);
    self.AddPair("round", ServerSettings(Level.CurrentServerSettings).RoundNumber+1);
    self.AddPair("numrounds", ServerSettings(Level.CurrentServerSettings).NumRounds);
    self.AddPair("timeleft", SwatGameReplicationInfo(Level.Game.GameReplicationInfo).RoundTime);

    switch (ServerSettings(Level.CurrentServerSettings).GameType)
    {
        #if IG_SPEECH_RECOGNITION
        case MPM_COOPQMM:
        #endif
        case MPM_COOP:
        case MPM_RapidDeployment:
        case MPM_VIPEscort:
            self.AddPair("timespecial", SwatGameReplicationInfo(Level.Game.GameReplicationInfo).SpecialTime);
            break;
    }

    switch (ServerSettings(Level.CurrentServerSettings).GameType)
    {
        #if IG_SPEECH_RECOGNITION
        case MPM_COOPQMM:
        #endif
        case MPM_COOP:
            self.AddCOOPInfo();
            break;
        case MPM_RapidDeployment:
            self.AddPair("bombsdefused", SwatGameReplicationInfo(Level.Game.GameReplicationInfo).DiffusedBombs);
            self.AddPair("bombstotal", SwatGameReplicationInfo(Level.Game.GameReplicationInfo).TotalNumberOfBombs);
            // no break
        default:
            self.AddPair("swatscore", SwatGameInfo(Level.Game).GetTeamFromID(0).NetScoreInfo.GetScore());
            self.AddPair("suspectsscore", SwatGameInfo(Level.Game).GetTeamFromID(1).NetScoreInfo.GetScore());
            self.AddPair("swatwon", SwatGameInfo(Level.Game).GetTeamFromID(0).NetScoreInfo.GetRoundsWon());
            self.AddPair("suspectswon", SwatGameInfo(Level.Game).GetTeamFromID(1).NetScoreInfo.GetRoundsWon());
            break;
    }
}

/**
 * Add
 *
 * @return  void
 */
public function AddCOOPInfo()
{
    local int i;
    local MissionObjectives Objectives;
    local Procedures Procedures;

    Objectives = SwatRepo(Level.GetRepo()).MissionObjectives;

    // "obj_"Objective name => Objective status pair
    for (i = 0; i < Objectives.Objectives.Length; i++)
    {
        // Skip the hidden objective
        if (Objectives.Objectives[i].name == 'Automatic_DoNot_Die')
        {
            continue;
        }
        self.AddPair("obj_" $ Objectives.Objectives[i].name, SwatGameReplicationInfo(Level.Game.GameReplicationInfo).ObjectiveStatus[i]);
    }

    // Add the params "tocreports" and "weaponssecured"
    Procedures = SwatRepo(Level.GetRepo()).Procedures;

    for (i = 0; i < Procedures.Procedures.Length; i++)
    {
        switch (Procedures.Procedures[i].class.name)
        {
            case 'Procedure_SecureAllWeapons':
                self.AddPair("tocreports", Procedures.Procedures[i].Status());
                break;
            case 'Procedure_ReportCharactersToTOC':
                self.AddPair("weaponssecured", Procedures.Procedures[i].Status());
                break;
        }
    }
}

/**
 * Append player details to response
 *
 * @return  void
 */
public function AddPlayers()
{
    local int i;
    local PlayerController PC;

    foreach DynamicActors(class'PlayerController', PC)
    {
        self.AddPair("player_" $ i, PC.PlayerReplicationInfo.PlayerName);
        self.AddPair("score_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetScore());
        self.AddPair("ping_" $ i, Min(999, PC.PlayerReplicationInfo.Ping));
        self.AddPair("team_" $ i, NetTeam(PC.PlayerReplicationInfo.Team).GetTeamNumber());

        // COOP status
        if (Level.IsCOOPServer)
        {
            self.AddPair("coopstatus_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).COOPPlayerStatus);
        }

        // Kills
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetEnemyKills() > 0)
        {
            self.AddPair("kills_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetEnemyKills());
        }

        // Deaths
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetTimesDied() > 0)
        {
            self.AddPair("deaths_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetTimesDied());
        }

        // Team Kills
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetFriendlyKills() > 0)
        {
            self.AddPair("tkills_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetFriendlyKills());
        }

        // Arrests
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetArrests() > 0)
        {
            self.AddPair("arrests_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetArrests());
        }

        // Arrested
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetTimesArrested() > 0)
        {
            self.AddPair("arrested_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetTimesArrested());
        }

        // The VIP
        if (SwatGamePlayerController(PC).ThisPlayerIsTheVIP)
        {
            self.AddPair("vip_" $ i, 1);
        }

        // VIP Escapes
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetVIPPlayerEscaped() > 0)
        {
            self.AddPair("vescaped_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetVIPPlayerEscaped());
        }

        // VIP Captures
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetArrestedVIP() > 0)
        {
            self.AddPair("arrestedvip_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetArrestedVIP());
        }

        // VIP Rescues
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetUnarrestedVIP() > 0)
        {
            self.AddPair("unarrestedvip_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetUnarrestedVIP());
        }

        // VIP Kills Valid
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetKilledVIPValid() > 0)
        {
            self.AddPair("validvipkills_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetKilledVIPValid());
        }

        // VIP Kills Invalid
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetKilledVIPInvalid() > 0)
        {
            self.AddPair("invalidvipkills_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetKilledVIPInvalid());
        }

        // Bombs Disarmed
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetBombsDiffused() > 0)
        {
            self.AddPair("bombsdiffused_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetBombsDiffused());
        }

        // RD Crybaby
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetRDCrybaby() > 0)
        {
            self.AddPair("rdcrybaby_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetRDCrybaby());
        }

        #if IG_SPEECH_RECOGNITION
        // SG Crybaby
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetSGCrybaby() > 0)
        {
            self.AddPair("sgcrybaby_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetSGCrybaby());
        }

        // Case Escapes
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetEscapedSG() > 0)
        {
            self.AddPair("escapedcase_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetEscapedSG());
        }

        // Case Kills
        if (SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetKilledSG() > 0)
        {
            self.AddPair("killedcase_" $ i, SwatPlayerReplicationInfo(PC.PlayerReplicationInfo).netScoreInfo.GetKilledSG());
        }
        #endif

        i++;
    }
}

/**
 * Add a new key-value pair
 *
 * @param   string Key
 * @param   string Value
 * @return  void
 */
public function AddPair(coerce string Key, coerce string Value)
{
    self.AddFragment(Key);
    self.AddFragment(Value);
}

/**
 * Append a new fragment to the list of fragments
 *
 * @param   string Fragment
 * @return  void
 */
protected function AddFragment(coerce string Fragment)
{
    // Trim long fragments
    self.Fragments[self.Fragments.Length] = Left(Fragment, class'Response'.const.MAX_PACKET_SIZE/3);
}

/**
 * Compute and return the length of given packet fragments
 * as if they were formed up into a packet using a backslash as the delimiter
 *
 * @param   array<string> Packet
 * @return  int
 */
static function int GetPacketSize(array<string> Packet)
{
    local int i, Size;

    for (i = 0; i < Packet.Length; i++)
    {
        Size += Len(Packet[i]);
    }
    return Size + Packet.Length;
}

/**
 * Tell whether the response instance has been occupied
 *
 * @return  bool
 */
public function bool IsOccupied()
{
    return self.bOccupied;
}

/**
 * Occupy the instance
 *
 * @return  void
 */
public function Occupy()
{
    self.bOccupied = true;
}

/**
 * Free the instance
 *
 * @return  void
 */
public function Free()
{
    self.Fragments.Remove(0, self.Fragments.Length);
    self.PacketCount = 0;
    self.bOccupied = false;
}

event Destroyed()
{
    self.Free();
    Super.Destroyed();
}

/* vim: set ft=java: */


Update filehandler.uc

Update filehandler.uc

Added support for Print # where there are multiple semicolons

# filehandler.uc - uCalc Transformation file
# This file was saved with uCalc Transform 2.95 on 3/13/2014 3:53:07 PM
# Comment: File handler

ExternalKeywords: Exclude, Comment, Selected, ParentChild, FindMode, InputFile, OutputFile, BatchAction, SEND
ExternalKeywords: Highlight, ForeColor, BackColor, FontName, FontSize, FontStyle
ExternalKeywords: FilterEndText, FilterSeparator, FilterSort, FilterSortFunc, FilterStartText, FilterUnique, FilterTally

FindMode: Replace

# Definitions

# Search Criteria

Criteria: 0
Enabled: True
Exclude: False
Comment: File handler
Selected: False
Highlight: True
ForeColor: ControlText
BackColor: Aqua
FontName:
FontSize:
FontStyle:
CaseSensitive: False
QuoteSensitive: True
CodeBlockSensitive: True
FilterEndText:
FilterSeparator: {#10}
FilterSort: False
FilterSortFunc:
FilterStartText:
FilterUnique: False
FilterTally: False
Min: 0
Max: -1
MinSoft: 0
MaxSoft: -1
BatchAction: Transform
InputFile:
OutputFile:
SEND:
StartAfter: 0
StopAfter: -1
SkipOver: False
ParentChild: 0
Pass: 0
PassOnce: True
Precedence: 0
RightToLeft: False

Criteria: 1
Comment:
Pass: 1

Criteria: 2
BackColor: Yellow
SkipOver: True
Find: ' {Comment:".*"}
Replace: [Skip over]

Criteria: 3
BackColor: DeepSkyBlue
Find: {@Start}
Replace: #include <iostream>
         #include <fstream>
         {@Var: FreeFileNum}

Criteria: 4
BackColor: DeepSkyBlue
Find: Open {filespec}
      { For {{Input: Input}|{Output: Output}|{Append: Append}|{Binary: Binary}|{Random: Random}}|{Default: } }
      { Access Read | Access Write | Access Read Write | }
      { Lock {{LockRead: Read}|{LockWrite: Write}|{LockBoth: Read Write}|{LockShared: Shared}}|{LockDefault: } }
      As [#]{filenum} [Len = {size=128}]
Replace: fstream file_{filenum} (string({filespec}).c_str(),
            {Input:   ios::in}
            {Output:  ios::out | ios::trunc}
            {Append:  ios::out | ios::app}
            {Binary:  ios::binary | ios::in | ios::out}
            {Random:  ios::binary | ios::in | ios::out}
            {Default: ios::binary | ios::in | ios::out}
         );

Criteria: 5
BackColor: Lime
Find: Close [#]{filenum}
Replace: file_{filenum}.close() ' PB: Close #{filenum}

Criteria: 6
BackColor: RoyalBlue
PassOnce: False
Find: Close [#]{file1}, {more}
Replace: Close {file1}
         Close {more}

Criteria: 7
BackColor: Pink
Find: Seek [#] {filenum}, {position}
Replace: file_{filenum}.seekg({position}-1)
         file_{filenum}.seekp({position}-1)

Criteria: 8
Find: Seek([#] {filenum})
Replace: file_{filenum}.tellg()+1

Criteria: 9
BackColor: SlateBlue
Find: LOF({filenum})
Replace: (missing)

Criteria: 10
Find: EOF({filenum})
Replace: file_{filenum}.eof()

Criteria: 11
Find: FreeFile
Replace: {@Eval: FreeFileNum += 1; FreeFileNum}

Criteria: 12
BackColor: Green
Find: Get$ [#] {filenum}, {count}, {StringVar}
Replace: {StringVar}.resize({count})
         file_{filenum}.read(&{StringVar}[0], {count})

Criteria: 13
BackColor: SandyBrown
Find: Put$ [#] {filenum}, {text}
Replace: file_{filenum}.write(&{text}[0], {text}.size())

Criteria: 14
Find: IsFile({file})
Replace: PB_ISFILE({file})

Criteria: 15
BackColor: Gold
Find: EXE.Extn$
Replace: PB_EXE_EXTN

Criteria: 16
BackColor: Silver
Find: EXE.Full$
Replace: PB_EXE_FULL()

Criteria: 17
Find: EXE.Name$
Replace: PB_EXE_NAME

Criteria: 18
BackColor: CornflowerBlue
Find: EXE.Namex$
Replace: PB_EXE_NAMEX

Criteria: 19
Find: EXE.Path$
Replace: PB_EXE_PATH

Criteria: 20
Find: Dir$({mask})
Replace: PB_DIR({mask})

Criteria: 21
Find: Dir$([Next])
Replace: PB_DIR_NEXT()

Criteria: 22
Find: Print #{filenum}, {text}
Replace: file_{filenum} << {text} << endl

Criteria: 23
Find: Print #{filenum}, {text};
Replace: file_{filenum} << {text}

Criteria: 24
Selected: True
PassOnce: False
Find: Print #{filenum}, {text}; {MoreText}
Replace: Print #{filenum}, {text} << {MoreText}

Criteria: 25
Comment:
Pass: 2

Criteria: 26
BackColor: Yellow
Find: fstream {file} ({args+})
Replace: {@Eval: Replace('{Self}', '{"[ \n]+"}', ' ')}

# End Search

# filehandler.uc - uCalc Transformation file
# This file was saved with uCalc Transform 2.95 on 3/12/2014 11:30:55 PM
# Comment: File handler

ExternalKeywords: Exclude, Comment, Selected, ParentChild, FindMode, InputFile, OutputFile, BatchAction, SEND
ExternalKeywords: Highlight, ForeColor, BackColor, FontName, FontSize, FontStyle
ExternalKeywords: FilterEndText, FilterSeparator, FilterSort, FilterSortFunc, FilterStartText, FilterUnique, FilterTally

FindMode: Replace

# Definitions

# Search Criteria

Criteria: 0
Enabled: True
Exclude: False
Comment: File handler
Selected: False
Highlight: True
ForeColor: ControlText
BackColor: Aqua
FontName:
FontSize:
FontStyle:
CaseSensitive: False
QuoteSensitive: True
CodeBlockSensitive: True
FilterEndText:
FilterSeparator: {#10}
FilterSort: False
FilterSortFunc:
FilterStartText:
FilterUnique: False
FilterTally: False
Min: 0
Max: -1
MinSoft: 0
MaxSoft: -1
BatchAction: Transform
InputFile:
OutputFile:
SEND:
StartAfter: 0
StopAfter: -1
SkipOver: False
ParentChild: 0
Pass: 0
PassOnce: True
Precedence: 0
RightToLeft: False

Criteria: 1
Comment:
Pass: 1

Criteria: 2
BackColor: Yellow
SkipOver: True
Find: ' {Comment:".*"}
Replace: [Skip over]

Criteria: 3
BackColor: DeepSkyBlue
Find: {@Start}
Replace: #include <iostream>
         #include <fstream>
         {@Var: FreeFileNum}

Criteria: 4
BackColor: DeepSkyBlue
Find: Open {filespec}
      { For {{Input: Input}|{Output: Output}|{Append: Append}|{Binary: Binary}|{Random: Random}}|{Default: } }
      { Access Read | Access Write | Access Read Write | }
      { Lock {{LockRead: Read}|{LockWrite: Write}|{LockBoth: Read Write}|{LockShared: Shared}}|{LockDefault: } }
      As [#]{filenum} [Len = {size=128}]
Replace: fstream file_{filenum} (string({filespec}).c_str(),
            {Input:   ios::in}
            {Output:  ios::out | ios::trunc}
            {Append:  ios::out | ios::app}
            {Binary:  ios::binary | ios::in | ios::out}
            {Random:  ios::binary | ios::in | ios::out}
            {Default: ios::binary | ios::in | ios::out}
         );

Criteria: 5
BackColor: Lime
Find: Close [#]{filenum}
Replace: file_{filenum}.close() ' PB: Close #{filenum}

Criteria: 6
BackColor: RoyalBlue
PassOnce: False
Find: Close [#]{file1}, {more}
Replace: Close {file1}
         Close {more}

Criteria: 7
BackColor: Pink
Find: Seek [#] {filenum}, {position}
Replace: file_{filenum}.seekg({position}-1)
         file_{filenum}.seekp({position}-1)

Criteria: 8
Find: Seek([#] {filenum})
Replace: file_{filenum}.tellg()+1

Criteria: 9
BackColor: SlateBlue
Find: LOF({filenum})
Replace: (missing)

Criteria: 10
Find: EOF({filenum})
Replace: file_{filenum}.eof()

Criteria: 11
Find: FreeFile
Replace: {@Eval: FreeFileNum += 1; FreeFileNum}

Criteria: 12
BackColor: Green
Find: Get$ [#] {filenum}, {count}, {StringVar}
Replace: {StringVar}.resize({count})
         file_{filenum}.read(&{StringVar}[0], {count})

Criteria: 13
BackColor: SandyBrown
Find: Put$ [#] {filenum}, {text}
Replace: file_{filenum}.write(&{text}[0], {text}.size())

Criteria: 14
Find: IsFile({file})
Replace: PB_ISFILE({file})

Criteria: 15
BackColor: Gold
Find: EXE.Extn$
Replace: PB_EXE_EXTN

Criteria: 16
BackColor: Silver
Find: EXE.Full$
Replace: PB_EXE_FULL()

Criteria: 17
Find: EXE.Name$
Replace: PB_EXE_NAME

Criteria: 18
BackColor: CornflowerBlue
Find: EXE.Namex$
Replace: PB_EXE_NAMEX

Criteria: 19
Find: EXE.Path$
Replace: PB_EXE_PATH

Criteria: 20
Find: Dir$({mask})
Replace: PB_DIR({mask})

Criteria: 21
Find: Dir$([Next])
Replace: PB_DIR_NEXT()

Criteria: 22
Find: Print #{filenum}, {text}
Replace: file_{filenum} << {text} << endl

Criteria: 23
Selected: True
Find: Print #{filenum}, {text};
Replace: file_{filenum} << {text}

Criteria: 24
Comment:
Pass: 2

Criteria: 25
BackColor: Yellow
Find: fstream {file} ({args+})
Replace: {@Eval: Replace('{Self}', '{"[ \n]+"}', ' ')}

# End Search


fix return value to actually use the modifiers

class X2AbilityToHitCalc_StatCheck extends X2AbilityToHitCalc abstract;

struct StatContestResultToEffectInfo
{
	var array<int> EffectIndices;
};

struct StatContestOverrideData
{
	var array<int> MultiTargetEffectsNumHits;
	var array<StatContestResultToEffectInfo> StatContestResultToEffectInfos;
};

var int BaseValue;

function int GetAttackValue(XComGameState_Ability kAbility, StateObjectReference TargetRef) { return -1; }
function int GetDefendValue(XComGameState_Ability kAbility, StateObjectReference TargetRef) { return -1; }

// start CHL issue #467
// CHL function modified: added conditions for X2Effect_ToHitModifier::GetHit(AsTarget)ModifiersForStatCheck
protected function int GetHitChance(XComGameState_Ability kAbility, AvailableTarget kTarget, optional out ShotBreakdown m_ShotBreakdown, optional bool bDebugLog = false)
{
	local XComGameState_Unit UnitState, TargetState;     /*  variables added  */
	local XComGameState_Effect EffectState;             /*    ###########    */
	local XComGameStateHistory History;                /*     #########     */
	local X2Effect_Persistent PersistentEffect;       /*      #######      */
	local StateObjectReference EffectRef;            /*       #####       */
	local array<ShotModifierInfo> ShotModifiers;    /*        ###        */
	local ShotModifierInfo ShotModifier;           /*     CHL #467      */
	local int AttackVal, DefendVal;
	local ShotBreakdown EmptyShotBreakdown;

	//reset shot breakdown
	m_ShotBreakdown = EmptyShotBreakdown;

	History = `XCOMHISTORY;
	UnitState = XComGameState_Unit(History.GetGameStateForObjectID(kAbility.OwnerStateObject.ObjectID));
	TargetState = XComGameState_Unit(History.GetGameStateForObjectID(kTarget.PrimaryTarget.ObjectID));

	AttackVal = GetAttackValue(kAbility, kTarget.PrimaryTarget);
	DefendVal = GetDefendValue(kAbility, kTarget.PrimaryTarget);

	// now lets check attacker's current effects
	foreach UnitState.AffectedByEffects(EffectRef)
	{
		EffectState = XComGameState_Effect(History.GetGameStateForObjectID(EffectRef.ObjectID));

		if (EffectState != None)
		{// check this effect's sub-class by attempting to cast it to what we need
			PersistentEffect = EffectState.GetX2Effect();

			if (PersistentEffect == None)
			{
				continue;
			}
		}
		// everything checked out let's add it to the list
		PersistentEffect.GetToHitModifiersForStatCheck(EffectState, UnitState, TargetState, kAbility, self.Class, ShotModifiers);
	}
	// repeat same process for the target's current effects
	foreach TargetState.AffectedByEffects(EffectRef)
	{
		EffectState = XComGameState_Effect(History.GetGameStateForObjectID(EffectRef.ObjectID));

		if (EffectState != None)
		{
			PersistentEffect = EffectState.GetX2Effect();

			if (PersistentEffect == None)
			{
				continue;
			}
		}

		PersistentEffect.GetToHitAsTargetModifiersForStatCheck(EffectState, UnitState, TargetState, kAbility, self.Class, ShotModifiers);
	}

	foreach ShotModifiers(ShotModifier)
	{
		AddModifier(ShotModifier.Value, ShotModifier.Reason, m_ShotBreakdown, ShotModifier.ModType, bDebugLog);
	}

	AddModifier(BaseValue, GetBaseString(), m_ShotBreakdown, eHit_Success, bDebugLog);
	AddModifier(AttackVal, GetAttackString(), m_ShotBreakdown, eHit_Success, bDebugLog);
	AddModifier(-DefendVal, GetDefendString(), m_ShotBreakdown, eHit_Success, bDebugLog);

	FinalizeHitChance(m_ShotBreakdown, bDebugLog);

	return m_ShotBreakdown.FinalHitChance;
}
// end CHL issue #467

function string GetBaseString() { return class'XLocalizedData'.default.BaseChance; }
function string GetAttackString() { return class'XLocalizedData'.default.OffenseStat; }
function string GetDefendString() { return class'XLocalizedData'.default.DefenseStat; }

function RollForAbilityHit(XComGameState_Ability kAbility, AvailableTarget kTarget, out AbilityResultContext ResultContext)
{
	local int MultiTargetIndex, AttackVal, DefendVal, TargetRoll, RandRoll, StatContestResultValue;
	local OverriddenEffectsByType EmptyOverriddenByType;
	local StatContestOverrideData StatContestOverrideInfo;
	local X2AbilityTemplate AbilityTemplate;

	`log("===RollForAbilityHit===",,'XCom_HitRolls');
	`log("Ability:" @ kAbility.GetMyTemplateName() @ "Target:" @ kTarget.PrimaryTarget.ObjectID,,'XCom_HitRolls');

	if (kTarget.PrimaryTarget.ObjectID > 0)
	{
		AttackVal = GetAttackValue(kAbility, kTarget.PrimaryTarget);
		DefendVal = GetDefendValue(kAbility, kTarget.PrimaryTarget);
		TargetRoll = BaseValue + AttackVal - DefendVal;
		`log("Attack Value:" @ AttackVal @ "Defend Value:" @ DefendVal @ "Target Roll:" @ TargetRoll,,'XCom_HitRolls');
		if (TargetRoll < 100)
		{
			RandRoll = `SYNC_RAND(100);
			`log("Random roll:" @ RandRoll,,'XCom_HitRolls');
			if (RandRoll < TargetRoll)
				ResultContext.HitResult = eHit_Success;
			else
				ResultContext.HitResult = eHit_Miss;
		}
		else
		{
			ResultContext.HitResult = eHit_Success;
		}
		`log("Result:" @ ResultContext.HitResult,,'XCom_HitRolls');
		if (class'XComGameStateContext_Ability'.static.IsHitResultHit(ResultContext.HitResult))
		{
			ResultContext.StatContestResult = RollForEffectTier(kAbility, kTarget.PrimaryTarget, false);
		}
	}
	else
	{
		ResultContext.HitResult = eHit_Success;         //  mark success for the ability to go off
	}

	if( `CHEATMGR != None && `CHEATMGR.bDeadEyeStats )
	{
		`log("DeadEyeStats cheat forcing a hit.", true, 'XCom_HitRolls');
		ResultContext.HitResult = eHit_Success;
	}
	else if( `CHEATMGR != None && `CHEATMGR.bNoLuckStats )
	{
		`log("NoLuckStats cheat forcing a miss.", true, 'XCom_HitRolls');
		ResultContext.HitResult = eHit_Miss;
	}
	if( `CHEATMGR != None && `CHEATMGR.bForceAttackRollValue )
	{
		ResultContext.HitResult = eHit_Success;
		ResultContext.StatContestResult = `CHEATMGR.iForcedRollValue;
	}

	// Set up the lookups to more quickly find the correct Effects values based on Tiers
	StatContestOverrideInfo.MultiTargetEffectsNumHits.Length = 0;
	StatContestOverrideInfo.StatContestResultToEffectInfos.Length = 0;

	AbilityTemplate = kAbility.GetMyTemplate();
	SetStatContestResultToEffectInfos(AbilityTemplate.AbilityMultiTargetEffects, GetHighestTierPossible(AbilityTemplate.AbilityMultiTargetEffects), StatContestOverrideInfo);

	for (MultiTargetIndex = 0; MultiTargetIndex < kTarget.AdditionalTargets.Length; ++MultiTargetIndex)
	{
		`log("Roll against multi target" @ kTarget.AdditionalTargets[MultiTargetIndex].ObjectID,,'XCom_HitRolls');
		AttackVal = GetAttackValue(kAbility, kTarget.AdditionalTargets[MultiTargetIndex]);
		DefendVal = GetDefendValue(kAbility, kTarget.AdditionalTargets[MultiTargetIndex]);
		TargetRoll = BaseValue + AttackVal - DefendVal;
		`log("Attack Value:" @ AttackVal @ "Defend Value:" @ DefendVal @ "Target Roll:" @ TargetRoll,,'XCom_HitRolls');
		if (TargetRoll < 100)
		{
			RandRoll = `SYNC_RAND(100);
			`log("Random roll:" @ RandRoll,,'XCom_HitRolls');
			if (RandRoll < TargetRoll)
				ResultContext.MultiTargetHitResults.AddItem(eHit_Success);
			else
				ResultContext.MultiTargetHitResults.AddItem(eHit_Miss);
		}
		else
		{
			ResultContext.MultiTargetHitResults.AddItem(eHit_Success);
		}
		`log("Result:" @ ResultContext.HitResult,,'XCom_HitRolls');
		if (class'XComGameStateContext_Ability'.static.IsHitResultHit(ResultContext.HitResult))
		{
			StatContestResultValue = RollForEffectTier(kAbility, kTarget.AdditionalTargets[MultiTargetIndex], true);
			ResultContext.MultiTargetStatContestResult.AddItem(StatContestResultValue);

			if (ResultContext.MultiTargetEffectsOverrides.Length <= MultiTargetIndex)
			{
				// A new Override needs to be added
				ResultContext.MultiTargetEffectsOverrides.AddItem(EmptyOverriddenByType);
			}

			// Check to see if this value needs to be changed
			// Ignore if the StatContestResultValue is 0
			// AND
			// If there are no Effects that need to be Overridden
			// AND
			// This StatContestResultValue does not map to Effects that may be Overridden
			if ((StatContestResultValue > 0) &&
				(StatContestOverrideInfo.StatContestResultToEffectInfos.Length > StatContestResultValue) &&
				(StatContestOverrideInfo.StatContestResultToEffectInfos[StatContestResultValue].EffectIndices.Length > 0))
			{
				DoStatContestResultOverrides(MultiTargetIndex, StatContestResultValue, kAbility, StatContestOverrideInfo, ResultContext);
			}
		}
		else
		{
			ResultContext.MultiTargetStatContestResult.AddItem(0);
		}
	}
}

function int RollForEffectTier(XComGameState_Ability kAbility, StateObjectReference TargetRef, bool bMultiTarget)
{
	local X2AbilityTemplate AbilityTemplate;
	local int MaxTier, MiddleTier, Idx, AttackVal, DefendVal;
	local array<float> TierValues;
	local float TierValue, LowTierValue, HighTierValue, TierValueSum, RandRoll;

	AbilityTemplate = kAbility.GetMyTemplate();
	if (TargetRef.ObjectID > 0)
	{
		`log("=RollForEffectTier=");
		AttackVal = GetAttackValue(kAbility, TargetRef);
		DefendVal = GetDefendValue(kAbility, TargetRef);
		if (bMultiTarget)
			MaxTier = GetHighestTierPossible(AbilityTemplate.AbilityMultiTargetEffects);
		else
			MaxTier = GetHighestTierPossible(AbilityTemplate.AbilityTargetEffects);
		`log("Attack Value:" @ AttackVal @ "Defend Value:" @ DefendVal @ "Max Tier:" @ MaxTier,,'XCom_HitRolls');

		//  It's possible the ability only cares about success or failure and has no specified ladder of results
		if (MaxTier < 0)
		{
			return 0;
		}

		MiddleTier = MaxTier / 2 + MaxTier % 2;
		TierValue = 100.0f / float(MaxTier);
		LowTierValue = TierValue * (float(DefendVal) / float(AttackVal));
		HighTierValue = TierValue * (float(AttackVal) / float(DefendVal));
		for (Idx = 1; Idx <= MaxTier; ++Idx)
		{
			if (Idx < MiddleTier)
			{
				TierValues.AddItem(LowTierValue);
			}
			else if (Idx == MiddleTier)
			{
				TierValues.AddItem(TierValue);
			}
			else
			{
				TierValues.AddItem(HighTierValue);
			}
			TierValueSum += TierValues[TierValues.Length - 1];
			`log("Tier" @ Idx $ ":" @ TierValues[TierValues.Length - 1],,'XCom_HitRolls');
		}
		//  Normalize the tier values
		for (Idx = 0; Idx < TierValues.Length; ++Idx)
		{
			TierValues[Idx] = TierValues[Idx] / TierValueSum;
			if (Idx > 0)
				TierValues[Idx] += TierValues[Idx - 1];

			`log("Normalized Tier" @ Idx $ ":" @ TierValues[Idx],,'XCom_HitRolls');
		}
		RandRoll = `SYNC_FRAND;
		`log("Random roll:" @ RandRoll,,'XCom_HitRolls');
		for (Idx = 0; Idx < TierValues.Length; ++Idx)
		{
			if (RandRoll < TierValues[Idx])
			{
				`log("Matched tier" @ Idx,,'XCom_HitRolls');
				return Idx + 1;     //  the lowest possible tier is 1, not 0
			}
		}
		`log("Matched highest tier",,'XCom_HitRolls');
		return TierValues.Length;
	}
	return 0;
}

protected function int GetHighestTierPossible(const array<X2Effect> TargetEffects)
{
	local int Highest, Idx;

	Highest = -1;
	for (Idx = 0; Idx < TargetEffects.Length; ++Idx)
	{
		//  ignore a minimum of 0 as the effect should always be applied
		if (TargetEffects[Idx].MinStatContestResult > 0 && TargetEffects[Idx].MinStatContestResult > Highest)
			Highest = TargetEffects[Idx].MinStatContestResult;

		//  ignore a maximum of 0 as the effect should always be applied (assuming min is also 0, but if it isn't, then that was already checked above)
		if (TargetEffects[Idx].MaxStatContestResult > 0 && TargetEffects[Idx].MaxStatContestResult > Highest)
			Highest = TargetEffects[Idx].MaxStatContestResult;
	}
	return Highest;
}

protected function SetStatContestResultToEffectInfos(const array<X2Effect> TargetEffects, int NumTiers, out StatContestOverrideData StatContestOverrideInfo)
{
	local int Idx, i;
	local bool bFoundAMaxNumberAllowedEffect;

	StatContestOverrideInfo.StatContestResultToEffectInfos.Length = NumTiers + 1;   // Just to make things easier, ignore the 0 index and use the StatResultContext values
	bFoundAMaxNumberAllowedEffect = false;

	StatContestOverrideInfo.MultiTargetEffectsNumHits.Length = 0;
	for (Idx = 0; Idx < TargetEffects.Length; ++Idx)
	{
		// This section is used to store data that may be needed for MultiTargetEffects that have a limited number of hits allowed
		// Ignore MinStatContestResults of 0
		if ((TargetEffects[Idx].MinStatContestResult > 0) && (TargetEffects[Idx].MultiTargetStatContestInfo.MaxNumberAllowed > 0))
		{
			bFoundAMaxNumberAllowedEffect = true;

			i = TargetEffects[Idx].MinStatContestResult;
			StatContestOverrideInfo.StatContestResultToEffectInfos[i].EffectIndices.AddItem(Idx);

			for (i = i + 1; i <= TargetEffects[Idx].MaxStatContestResult; ++i)
			{
				StatContestOverrideInfo.StatContestResultToEffectInfos[i].EffectIndices.AddItem(Idx);
			}
		}

		// Zero out the NumHits for this Effect
		StatContestOverrideInfo.MultiTargetEffectsNumHits.AddItem(0);
	}

	if (!bFoundAMaxNumberAllowedEffect)
	{
		// Clear the length since this is used to early out future checks
		StatContestOverrideInfo.StatContestResultToEffectInfos.Length = 0;
	}
}

protected function DoStatContestResultOverrides(int MultiTargetIndex, int StatContestResultValue, XComGameState_Ability kAbility,
												out StatContestOverrideData StatContestOverrideInfo, out AbilityResultContext ResultContext)
{
	local int Index, EffectIdx, ReplaceWithIdx;
	local X2AbilityTemplate AbilityTemplate;
	local bool bContinueLooking;
	local int TypeIndex;
	local OverriddenEffectsInfo NewOverrideInfo;

	TypeIndex = ResultContext.MultiTargetEffectsOverrides[MultiTargetIndex].OverrideInfo.Find('OverrideType', 'EffectOverride_StatContest');
	if (TypeIndex == INDEX_NONE)
	{
		TypeIndex = ResultContext.MultiTargetEffectsOverrides[MultiTargetIndex].OverrideInfo.Length;
		NewOverrideInfo.OverrideType = 'EffectOverride_StatContest';
		ResultContext.MultiTargetEffectsOverrides[MultiTargetIndex].OverrideInfo.AddItem(NewOverrideInfo);
	}

	AbilityTemplate = kAbility.GetMyTemplate();

	for (Index = 0; Index < StatContestOverrideInfo.StatContestResultToEffectInfos[StatContestResultValue].EffectIndices.Length; ++Index)
	{
		// Loop over the Effects found at this StatContestResult and decide what effect should
		// actually be applied
		EffectIdx = StatContestOverrideInfo.StatContestResultToEffectInfos[StatContestResultValue].EffectIndices[Index];

		ReplaceWithIdx = EffectIdx;
		bContinueLooking = true;
		while ((ReplaceWithIdx != INDEX_NONE) &&
				(ReplaceWithIdx < StatContestOverrideInfo.MultiTargetEffectsNumHits.Length) &&
				bContinueLooking)
		{
			++StatContestOverrideInfo.MultiTargetEffectsNumHits[ReplaceWithIdx];

			// Continue Looking if the Effect has a MaxNumberAllowed (>0)
			// AND
			// That MaxNumberAllowed has been exceeded
			bContinueLooking = (AbilityTemplate.AbilityMultiTargetEffects[ReplaceWithIdx].MultiTargetStatContestInfo.MaxNumberAllowed > 0) &&
							   (StatContestOverrideInfo.MultiTargetEffectsNumHits[ReplaceWithIdx] > AbilityTemplate.AbilityMultiTargetEffects[ReplaceWithIdx].MultiTargetStatContestInfo.MaxNumberAllowed);
			if (bContinueLooking)
			{
				// The max number of this Effect is applied, change what Effect will actually get applied here
				ReplaceWithIdx = AbilityTemplate.AbilityMultiTargetEffects[ReplaceWithIdx].MultiTargetStatContestInfo.EffectIdxToApplyOnMaxExceeded;
			}
		}

		if (EffectIdx != ReplaceWithIdx)
		{
			// This MultiTarget Effect has been applied too many times and needs to be replaced
			ResultContext.MultiTargetEffectsOverrides[MultiTargetIndex].OverrideInfo[TypeIndex].OverriddenEffects.AddItem(AbilityTemplate.AbilityMultiTargetEffects[EffectIdx]);
			if (ReplaceWithIdx != INDEX_NONE)
			{
				ResultContext.MultiTargetEffectsOverrides[MultiTargetIndex].OverrideInfo[TypeIndex].OverriddingEffects.AddItem(AbilityTemplate.AbilityMultiTargetEffects[ReplaceWithIdx]);
			}
		}
	}
}

DefaultProperties
{
	BaseValue = 75
}

class X2AbilityToHitCalc_StatCheck extends X2AbilityToHitCalc abstract;

struct StatContestResultToEffectInfo
{
	var array<int> EffectIndices;
};

struct StatContestOverrideData
{
	var array<int> MultiTargetEffectsNumHits;
	var array<StatContestResultToEffectInfo> StatContestResultToEffectInfos;
};

var int BaseValue;

function int GetAttackValue(XComGameState_Ability kAbility, StateObjectReference TargetRef) { return -1; }
function int GetDefendValue(XComGameState_Ability kAbility, StateObjectReference TargetRef) { return -1; }

// start CHL issue #467
// CHL function modified: added conditions for X2Effect_ToHitModifier::GetHit(AsTarget)ModifiersForStatCheck
protected function int GetHitChance(XComGameState_Ability kAbility, AvailableTarget kTarget, optional out ShotBreakdown m_ShotBreakdown, optional bool bDebugLog = false)
{
	local XComGameState_Unit UnitState, TargetState;     /*  variables added  */
	local XComGameState_Effect EffectState;             /*    ###########    */
	local XComGameStateHistory History;                /*     #########     */
	local X2Effect_Persistent PersistentEffect;       /*      #######      */
	local StateObjectReference EffectRef;            /*       #####       */
	local array<ShotModifierInfo> ShotModifiers;    /*        ###        */
	local ShotModifierInfo ShotModifier;           /*     CHL #467      */
	local int AttackVal, DefendVal, TargetRoll;
	local ShotBreakdown EmptyShotBreakdown;

	//reset shot breakdown
	m_ShotBreakdown = EmptyShotBreakdown;

	History = `XCOMHISTORY;
	UnitState = XComGameState_Unit(History.GetGameStateForObjectID(kAbility.OwnerStateObject.ObjectID));
	TargetState = XComGameState_Unit(History.GetGameStateForObjectID(kTarget.PrimaryTarget.ObjectID));

	AttackVal = GetAttackValue(kAbility, kTarget.PrimaryTarget);
	DefendVal = GetDefendValue(kAbility, kTarget.PrimaryTarget);
	TargetRoll = BaseValue + AttackVal - DefendVal;

	// now lets check attacker's current effects
	foreach UnitState.AffectedByEffects(EffectRef)
	{
		EffectState = XComGameState_Effect(History.GetGameStateForObjectID(EffectRef.ObjectID));

		if (EffectState != None)
		{// check this effect's sub-class by attempting to cast it to what we need
			PersistentEffect = EffectState.GetX2Effect();

			if (PersistentEffect == None)
			{
				continue;
			}
		}
		// everything checked out let's add it to the list
		PersistentEffect.GetToHitModifiersForStatCheck(EffectState, UnitState, TargetState, kAbility, self.Class, ShotModifiers);
	}
	// repeat same process for the target's current effects
	foreach TargetState.AffectedByEffects(EffectRef)
	{
		EffectState = XComGameState_Effect(History.GetGameStateForObjectID(EffectRef.ObjectID));

		if (EffectState != None)
		{
			PersistentEffect = EffectState.GetX2Effect();

			if (PersistentEffect == None)
			{
				continue;
			}
		}

		PersistentEffect.GetToHitAsTargetModifiersForStatCheck(EffectState, UnitState, TargetState, kAbility, self.Class, ShotModifiers);
	}

	foreach ShotModifiers(ShotModifier)
	{
		AddModifier(ShotModifier.Value, ShotModifier.Reason, m_ShotBreakdown, ShotModifier.ModType, bDebugLog);
	}

	AddModifier(BaseValue, GetBaseString(), m_ShotBreakdown, eHit_Success, bDebugLog);
	AddModifier(AttackVal, GetAttackString(), m_ShotBreakdown, eHit_Success, bDebugLog);
	AddModifier(-DefendVal, GetDefendString(), m_ShotBreakdown, eHit_Success, bDebugLog);

	m_ShotBreakdown.FinalHitChance = TargetRoll;

	return TargetRoll;
}
// end CHL issue #467

function string GetBaseString() { return class'XLocalizedData'.default.BaseChance; }
function string GetAttackString() { return class'XLocalizedData'.default.OffenseStat; }
function string GetDefendString() { return class'XLocalizedData'.default.DefenseStat; }

function RollForAbilityHit(XComGameState_Ability kAbility, AvailableTarget kTarget, out AbilityResultContext ResultContext)
{
	local int MultiTargetIndex, AttackVal, DefendVal, TargetRoll, RandRoll, StatContestResultValue;
	local OverriddenEffectsByType EmptyOverriddenByType;
	local StatContestOverrideData StatContestOverrideInfo;
	local X2AbilityTemplate AbilityTemplate;

	`log("===RollForAbilityHit===",,'XCom_HitRolls');
	`log("Ability:" @ kAbility.GetMyTemplateName() @ "Target:" @ kTarget.PrimaryTarget.ObjectID,,'XCom_HitRolls');

	if (kTarget.PrimaryTarget.ObjectID > 0)
	{
		AttackVal = GetAttackValue(kAbility, kTarget.PrimaryTarget);
		DefendVal = GetDefendValue(kAbility, kTarget.PrimaryTarget);
		TargetRoll = BaseValue + AttackVal - DefendVal;
		`log("Attack Value:" @ AttackVal @ "Defend Value:" @ DefendVal @ "Target Roll:" @ TargetRoll,,'XCom_HitRolls');
		if (TargetRoll < 100)
		{
			RandRoll = `SYNC_RAND(100);
			`log("Random roll:" @ RandRoll,,'XCom_HitRolls');
			if (RandRoll < TargetRoll)
				ResultContext.HitResult = eHit_Success;
			else
				ResultContext.HitResult = eHit_Miss;
		}
		else
		{
			ResultContext.HitResult = eHit_Success;
		}
		`log("Result:" @ ResultContext.HitResult,,'XCom_HitRolls');
		if (class'XComGameStateContext_Ability'.static.IsHitResultHit(ResultContext.HitResult))
		{
			ResultContext.StatContestResult = RollForEffectTier(kAbility, kTarget.PrimaryTarget, false);
		}
	}
	else
	{
		ResultContext.HitResult = eHit_Success;         //  mark success for the ability to go off
	}

	if( `CHEATMGR != None && `CHEATMGR.bDeadEyeStats )
	{
		`log("DeadEyeStats cheat forcing a hit.", true, 'XCom_HitRolls');
		ResultContext.HitResult = eHit_Success;
	}
	else if( `CHEATMGR != None && `CHEATMGR.bNoLuckStats )
	{
		`log("NoLuckStats cheat forcing a miss.", true, 'XCom_HitRolls');
		ResultContext.HitResult = eHit_Miss;
	}
	if( `CHEATMGR != None && `CHEATMGR.bForceAttackRollValue )
	{
		ResultContext.HitResult = eHit_Success;
		ResultContext.StatContestResult = `CHEATMGR.iForcedRollValue;
	}

	// Set up the lookups to more quickly find the correct Effects values based on Tiers
	StatContestOverrideInfo.MultiTargetEffectsNumHits.Length = 0;
	StatContestOverrideInfo.StatContestResultToEffectInfos.Length = 0;

	AbilityTemplate = kAbility.GetMyTemplate();
	SetStatContestResultToEffectInfos(AbilityTemplate.AbilityMultiTargetEffects, GetHighestTierPossible(AbilityTemplate.AbilityMultiTargetEffects), StatContestOverrideInfo);

	for (MultiTargetIndex = 0; MultiTargetIndex < kTarget.AdditionalTargets.Length; ++MultiTargetIndex)
	{
		`log("Roll against multi target" @ kTarget.AdditionalTargets[MultiTargetIndex].ObjectID,,'XCom_HitRolls');
		AttackVal = GetAttackValue(kAbility, kTarget.AdditionalTargets[MultiTargetIndex]);
		DefendVal = GetDefendValue(kAbility, kTarget.AdditionalTargets[MultiTargetIndex]);
		TargetRoll = BaseValue + AttackVal - DefendVal;
		`log("Attack Value:" @ AttackVal @ "Defend Value:" @ DefendVal @ "Target Roll:" @ TargetRoll,,'XCom_HitRolls');
		if (TargetRoll < 100)
		{
			RandRoll = `SYNC_RAND(100);
			`log("Random roll:" @ RandRoll,,'XCom_HitRolls');
			if (RandRoll < TargetRoll)
				ResultContext.MultiTargetHitResults.AddItem(eHit_Success);
			else
				ResultContext.MultiTargetHitResults.AddItem(eHit_Miss);
		}
		else
		{
			ResultContext.MultiTargetHitResults.AddItem(eHit_Success);
		}
		`log("Result:" @ ResultContext.HitResult,,'XCom_HitRolls');
		if (class'XComGameStateContext_Ability'.static.IsHitResultHit(ResultContext.HitResult))
		{
			StatContestResultValue = RollForEffectTier(kAbility, kTarget.AdditionalTargets[MultiTargetIndex], true);
			ResultContext.MultiTargetStatContestResult.AddItem(StatContestResultValue);

			if (ResultContext.MultiTargetEffectsOverrides.Length <= MultiTargetIndex)
			{
				// A new Override needs to be added
				ResultContext.MultiTargetEffectsOverrides.AddItem(EmptyOverriddenByType);
			}

			// Check to see if this value needs to be changed
			// Ignore if the StatContestResultValue is 0
			// AND
			// If there are no Effects that need to be Overridden
			// AND
			// This StatContestResultValue does not map to Effects that may be Overridden
			if ((StatContestResultValue > 0) &&
				(StatContestOverrideInfo.StatContestResultToEffectInfos.Length > StatContestResultValue) &&
				(StatContestOverrideInfo.StatContestResultToEffectInfos[StatContestResultValue].EffectIndices.Length > 0))
			{
				DoStatContestResultOverrides(MultiTargetIndex, StatContestResultValue, kAbility, StatContestOverrideInfo, ResultContext);
			}
		}
		else
		{
			ResultContext.MultiTargetStatContestResult.AddItem(0);
		}
	}
}

function int RollForEffectTier(XComGameState_Ability kAbility, StateObjectReference TargetRef, bool bMultiTarget)
{
	local X2AbilityTemplate AbilityTemplate;
	local int MaxTier, MiddleTier, Idx, AttackVal, DefendVal;
	local array<float> TierValues;
	local float TierValue, LowTierValue, HighTierValue, TierValueSum, RandRoll;

	AbilityTemplate = kAbility.GetMyTemplate();
	if (TargetRef.ObjectID > 0)
	{
		`log("=RollForEffectTier=");
		AttackVal = GetAttackValue(kAbility, TargetRef);
		DefendVal = GetDefendValue(kAbility, TargetRef);
		if (bMultiTarget)
			MaxTier = GetHighestTierPossible(AbilityTemplate.AbilityMultiTargetEffects);
		else
			MaxTier = GetHighestTierPossible(AbilityTemplate.AbilityTargetEffects);
		`log("Attack Value:" @ AttackVal @ "Defend Value:" @ DefendVal @ "Max Tier:" @ MaxTier,,'XCom_HitRolls');

		//  It's possible the ability only cares about success or failure and has no specified ladder of results
		if (MaxTier < 0)
		{
			return 0;
		}

		MiddleTier = MaxTier / 2 + MaxTier % 2;
		TierValue = 100.0f / float(MaxTier);
		LowTierValue = TierValue * (float(DefendVal) / float(AttackVal));
		HighTierValue = TierValue * (float(AttackVal) / float(DefendVal));
		for (Idx = 1; Idx <= MaxTier; ++Idx)
		{
			if (Idx < MiddleTier)
			{
				TierValues.AddItem(LowTierValue);
			}
			else if (Idx == MiddleTier)
			{
				TierValues.AddItem(TierValue);
			}
			else
			{
				TierValues.AddItem(HighTierValue);
			}
			TierValueSum += TierValues[TierValues.Length - 1];
			`log("Tier" @ Idx $ ":" @ TierValues[TierValues.Length - 1],,'XCom_HitRolls');
		}
		//  Normalize the tier values
		for (Idx = 0; Idx < TierValues.Length; ++Idx)
		{
			TierValues[Idx] = TierValues[Idx] / TierValueSum;
			if (Idx > 0)
				TierValues[Idx] += TierValues[Idx - 1];

			`log("Normalized Tier" @ Idx $ ":" @ TierValues[Idx],,'XCom_HitRolls');
		}
		RandRoll = `SYNC_FRAND;
		`log("Random roll:" @ RandRoll,,'XCom_HitRolls');
		for (Idx = 0; Idx < TierValues.Length; ++Idx)
		{
			if (RandRoll < TierValues[Idx])
			{
				`log("Matched tier" @ Idx,,'XCom_HitRolls');
				return Idx + 1;     //  the lowest possible tier is 1, not 0
			}
		}
		`log("Matched highest tier",,'XCom_HitRolls');
		return TierValues.Length;
	}
	return 0;
}

protected function int GetHighestTierPossible(const array<X2Effect> TargetEffects)
{
	local int Highest, Idx;

	Highest = -1;
	for (Idx = 0; Idx < TargetEffects.Length; ++Idx)
	{
		//  ignore a minimum of 0 as the effect should always be applied
		if (TargetEffects[Idx].MinStatContestResult > 0 && TargetEffects[Idx].MinStatContestResult > Highest)
			Highest = TargetEffects[Idx].MinStatContestResult;

		//  ignore a maximum of 0 as the effect should always be applied (assuming min is also 0, but if it isn't, then that was already checked above)
		if (TargetEffects[Idx].MaxStatContestResult > 0 && TargetEffects[Idx].MaxStatContestResult > Highest)
			Highest = TargetEffects[Idx].MaxStatContestResult;
	}
	return Highest;
}

protected function SetStatContestResultToEffectInfos(const array<X2Effect> TargetEffects, int NumTiers, out StatContestOverrideData StatContestOverrideInfo)
{
	local int Idx, i;
	local bool bFoundAMaxNumberAllowedEffect;

	StatContestOverrideInfo.StatContestResultToEffectInfos.Length = NumTiers + 1;   // Just to make things easier, ignore the 0 index and use the StatResultContext values
	bFoundAMaxNumberAllowedEffect = false;

	StatContestOverrideInfo.MultiTargetEffectsNumHits.Length = 0;
	for (Idx = 0; Idx < TargetEffects.Length; ++Idx)
	{
		// This section is used to store data that may be needed for MultiTargetEffects that have a limited number of hits allowed
		// Ignore MinStatContestResults of 0
		if ((TargetEffects[Idx].MinStatContestResult > 0) && (TargetEffects[Idx].MultiTargetStatContestInfo.MaxNumberAllowed > 0))
		{
			bFoundAMaxNumberAllowedEffect = true;

			i = TargetEffects[Idx].MinStatContestResult;
			StatContestOverrideInfo.StatContestResultToEffectInfos[i].EffectIndices.AddItem(Idx);

			for (i = i + 1; i <= TargetEffects[Idx].MaxStatContestResult; ++i)
			{
				StatContestOverrideInfo.StatContestResultToEffectInfos[i].EffectIndices.AddItem(Idx);
			}
		}

		// Zero out the NumHits for this Effect
		StatContestOverrideInfo.MultiTargetEffectsNumHits.AddItem(0);
	}

	if (!bFoundAMaxNumberAllowedEffect)
	{
		// Clear the length since this is used to early out future checks
		StatContestOverrideInfo.StatContestResultToEffectInfos.Length = 0;
	}
}

protected function DoStatContestResultOverrides(int MultiTargetIndex, int StatContestResultValue, XComGameState_Ability kAbility,
												out StatContestOverrideData StatContestOverrideInfo, out AbilityResultContext ResultContext)
{
	local int Index, EffectIdx, ReplaceWithIdx;
	local X2AbilityTemplate AbilityTemplate;
	local bool bContinueLooking;
	local int TypeIndex;
	local OverriddenEffectsInfo NewOverrideInfo;

	TypeIndex = ResultContext.MultiTargetEffectsOverrides[MultiTargetIndex].OverrideInfo.Find('OverrideType', 'EffectOverride_StatContest');
	if (TypeIndex == INDEX_NONE)
	{
		TypeIndex = ResultContext.MultiTargetEffectsOverrides[MultiTargetIndex].OverrideInfo.Length;
		NewOverrideInfo.OverrideType = 'EffectOverride_StatContest';
		ResultContext.MultiTargetEffectsOverrides[MultiTargetIndex].OverrideInfo.AddItem(NewOverrideInfo);
	}

	AbilityTemplate = kAbility.GetMyTemplate();

	for (Index = 0; Index < StatContestOverrideInfo.StatContestResultToEffectInfos[StatContestResultValue].EffectIndices.Length; ++Index)
	{
		// Loop over the Effects found at this StatContestResult and decide what effect should
		// actually be applied
		EffectIdx = StatContestOverrideInfo.StatContestResultToEffectInfos[StatContestResultValue].EffectIndices[Index];

		ReplaceWithIdx = EffectIdx;
		bContinueLooking = true;
		while ((ReplaceWithIdx != INDEX_NONE) &&
				(ReplaceWithIdx < StatContestOverrideInfo.MultiTargetEffectsNumHits.Length) &&
				bContinueLooking)
		{
			++StatContestOverrideInfo.MultiTargetEffectsNumHits[ReplaceWithIdx];

			// Continue Looking if the Effect has a MaxNumberAllowed (>0)
			// AND
			// That MaxNumberAllowed has been exceeded
			bContinueLooking = (AbilityTemplate.AbilityMultiTargetEffects[ReplaceWithIdx].MultiTargetStatContestInfo.MaxNumberAllowed > 0) &&
							   (StatContestOverrideInfo.MultiTargetEffectsNumHits[ReplaceWithIdx] > AbilityTemplate.AbilityMultiTargetEffects[ReplaceWithIdx].MultiTargetStatContestInfo.MaxNumberAllowed);
			if (bContinueLooking)
			{
				// The max number of this Effect is applied, change what Effect will actually get applied here
				ReplaceWithIdx = AbilityTemplate.AbilityMultiTargetEffects[ReplaceWithIdx].MultiTargetStatContestInfo.EffectIdxToApplyOnMaxExceeded;
			}
		}

		if (EffectIdx != ReplaceWithIdx)
		{
			// This MultiTarget Effect has been applied too many times and needs to be replaced
			ResultContext.MultiTargetEffectsOverrides[MultiTargetIndex].OverrideInfo[TypeIndex].OverriddenEffects.AddItem(AbilityTemplate.AbilityMultiTargetEffects[EffectIdx]);
			if (ReplaceWithIdx != INDEX_NONE)
			{
				ResultContext.MultiTargetEffectsOverrides[MultiTargetIndex].OverrideInfo[TypeIndex].OverriddingEffects.AddItem(AbilityTemplate.AbilityMultiTargetEffects[ReplaceWithIdx]);
			}
		}
	}
}

DefaultProperties
{
	BaseValue = 75
}


Update filehandler.uc

Update filehandler.uc

Added FreeFile, Get$, Put$
Fixed Open

# filehandler.uc - uCalc Transformation file
# This file was saved with uCalc Transform 2.5 on 2/6/2014 4:49:22 PM
# Comment: File handler

ExternalKeywords: Exclude, Comment, Selected, ParentChild, FindMode, OutputFile, BatchAction, SEND
ExternalKeywords: Highlight, ForeColor, BackColor, FontName, FontSize, FontStyle
ExternalKeywords: FilterEndText, FilterSeparator, FilterSort, FilterSortFunc, FilterStartText, FilterUnique

FindMode: Replace

# Definitions

# Search Criteria

Criteria: 0
Enabled: True
Exclude: False
Comment: File handler
Selected: False
Highlight: True
ForeColor: ControlText
BackColor: Aqua
FontName:
FontSize:
FontStyle:
CaseSensitive: False
QuoteSensitive: True
CodeBlockSensitive: True
FilterEndText:
FilterSeparator: {#10}
FilterSort: False
FilterSortFunc:
FilterStartText:
FilterUnique: False
Min: 0
Max: -1
MinSoft: 0
MaxSoft: -1
BatchAction: Transform
OutputFile:
SEND:
StartAfter: 0
StopAfter: -1
SkipOver: False
ParentChild: 0
Pass: 0
PassOnce: True
Precedence: 0
RightToLeft: False

Criteria: 1
Comment:
Pass: 1

Criteria: 2
BackColor: Yellow
SkipOver: True
Find: ' {Comment:".*"}
Replace: [Skip over]

Criteria: 3
BackColor: DeepSkyBlue
Find: {@Start}
Replace: #include <iostream>
         #include <fstream>
         {@Var: FreeFileNum}

Criteria: 4
Selected: True
BackColor: DeepSkyBlue
Find: Open {filespec}
      { For {{Input: Input}|{Output: Output}|{Append: Append}|{Binary: Binary}|{Random: Random}}|{Default: } }
      { Access Read | Access Write | Access Read Write | }
      { Lock {{LockRead: Read}|{LockWrite: Write}|{LockBoth: Read Write}|{LockShared: Shared}}|{LockDefault: } }
      As [#]{filenum} [Len = {size=128}]
Replace: fstream file_{filenum} (string({filespec}).c_str(),
            {Input: ios::in}
            {Output: ios::out | ios::trunc}
            {Append: ios::out | ios::app}
            {Binary: ios::binary | ios::out}
            {Random: ios::binary | ios::out}
            {Default: ios::binary | ios::out}
         );

Criteria: 5
BackColor: Lime
Find: Close [#]{filenum}
Replace: file_{filenum}.close() ' PB: Close #{filenum}

Criteria: 6
BackColor: RoyalBlue
PassOnce: False
Find: Close [#]{file1}, {more}
Replace: Close {file1}
         Close {more}

Criteria: 7
BackColor: Pink
Find: Seek [#] {filenum}, {position}
Replace: file_{filenum}.seekg({position}-1)
         file_{filenum}.seekp({position}-1)

Criteria: 8
Find: Seek([#] {filenum})
Replace: file_{filenum}.tellg()+1

Criteria: 9
BackColor: SlateBlue
Find: LOF({filenum})
Replace: (missing)

Criteria: 10
Find: EOF({filenum})
Replace: file_{filenum}.eof()

Criteria: 11
Find: FreeFile
Replace: {@Eval: FreeFileNum += 1; FreeFileNum}

Criteria: 12
BackColor: Green
Find: Get$ [#] {filenum}, {count}, {StringVar}
Replace: {StringVar}.resize({count})
         file_{filenum}.read(&{StringVar}[0], {count})

Criteria: 13
BackColor: SandyBrown
Find: Put$ [#] {filenum}, {text}
Replace: file_{filenum}.write(&{text}[0], {text}.size())

Criteria: 14
Comment:
Pass: 2

Criteria: 15
BackColor: Yellow
Find: fstream {file} ({args+})
Replace: {@Eval: Replace('{Self}', '{"[ \n]+"}', ' ')}

# End Search

# filehandler.uc - uCalc Transformation file
# This file was saved with uCalc Transform 2.5 on 2/5/2014 5:31:05 PM
# Comment: File handler

ExternalKeywords: Exclude, Comment, Selected, ParentChild, FindMode, OutputFile, BatchAction, SEND
ExternalKeywords: Highlight, ForeColor, BackColor, FontName, FontSize, FontStyle
ExternalKeywords: FilterEndText, FilterSeparator, FilterSort, FilterSortFunc, FilterStartText, FilterUnique

FindMode: Replace

# Definitions

# Search Criteria

Criteria: 0
Enabled: True
Exclude: False
Comment: File handler
Selected: False
Highlight: True
ForeColor: ControlText
BackColor: Aqua
FontName:
FontSize:
FontStyle:
CaseSensitive: False
QuoteSensitive: True
CodeBlockSensitive: True
FilterEndText:
FilterSeparator: {#10}
FilterSort: False
FilterSortFunc:
FilterStartText:
FilterUnique: False
Min: 0
Max: -1
MinSoft: 0
MaxSoft: -1
BatchAction: Transform
OutputFile:
SEND:
StartAfter: 0
StopAfter: -1
SkipOver: False
ParentChild: 0
Pass: 0
PassOnce: True
Precedence: 0
RightToLeft: False

Criteria: 1
Comment:
Pass: 1

Criteria: 2
BackColor: Yellow
SkipOver: True
Find: ' {Comment:".*"}
Replace: [Skip over]

Criteria: 3
BackColor: DeepSkyBlue
Find: {@Start}
Replace: #include <iostream>
         #include <fstream>

Criteria: 4
Selected: True
BackColor: DeepSkyBlue
Find: Open {filespec}
      { For {{Input: Input}|{Output: Output}|{Append: Append}|{Binary: Binary}|{Random: Random}}|{Default: } }
      { Access Read | Access Write | Access Read Write | }
      { Lock {{LockRead: Read}|{LockWrite: Write}|{LockBoth: Read Write}|{LockShared: Shared}}|{LockDefault: } }
      As [#]{filenum} [Len = {size=128}]
Replace: ' PB: {Self}
         fstream file_{filenum} ({filespec},
            {Input: ios::in}
            {Output: ios::out Or ios::trunc}
            {Append: ios::out Or ios::app}
            {Binary: ios::binary Or ios::out}
            {Random: ios::binary Or ios::out}
            {Default: ios::binary Or ios::out}
         );

Criteria: 5
BackColor: Lime
Find: Close [#]{filenum}
Replace: ' PB: Close #{filenum}
         file_{filenum}.close()

Criteria: 6
BackColor: RoyalBlue
PassOnce: False
Find: Close [#]{file1}, {more}
Replace: Close {file1}
         Close {more}

Criteria: 7
BackColor: Pink
Find: Seek [#] {filenum}, {position}
Replace: file_{filenum}.seekg({position}-1)
         file_{filenum}.seekp({position}-1)

Criteria: 8
Find: Seek([#] {filenum})
Replace: file_{filenum}.tellg()+1

Criteria: 9
BackColor: SlateBlue
Find: LOF({filenum})
Replace: (missing)

Criteria: 10
Find: EOF({filenum})
Replace: file_{filenum}.eof()

Criteria: 11
Comment:
Pass: 2

Criteria: 12
BackColor: Yellow
Find: fstream {file} ({args})
Replace: {@Eval: Replace('{Self}', '{"[ \n]+"}', ' ')}

# End Search


Update beautify.uc

Update beautify.uc

Replaces {#3} with {Q} for compatibility with current uCalc Transform version

# beautify.uc - uCalc Transformation file
# This file was saved with uCalc Transform 2.5 on 1/14/2014 6:37:14 PM
# Comment: Fixes indentation and spacing in C++ source code

ExternalKeywords: Exclude, Comment, Selected, ParentChild, FindMode, OutputFile, BatchAction, SEND
ExternalKeywords: Highlight, ForeColor, BackColor, FontName, FontSize, FontStyle
ExternalKeywords: FilterEndText, FilterSeparator, FilterSort, FilterSortFunc, FilterStartText, FilterUnique

FindMode: Replace

# Definitions

# Search Criteria

Criteria: 0
Enabled: True
Exclude: False
Comment: Fixes indentation and spacing in C++ source code
Selected: False
Highlight: False
ForeColor: ControlText
BackColor: Aqua
FontName:
FontSize:
FontStyle:
CaseSensitive: False
QuoteSensitive: True
CodeBlockSensitive: True
FilterEndText:
FilterSeparator: {#10}
FilterSort: False
FilterSortFunc:
FilterStartText:
FilterUnique: False
Min: 0
Max: -1
MinSoft: 0
MaxSoft: -1
BatchAction: Transform
OutputFile:
SEND:
StartAfter: 0
StopAfter: -1
SkipOver: False
ParentChild: 0
Pass: 0
PassOnce: True
Precedence: 0
RightToLeft: False

Criteria: 1
Find:
Replace: {@Define:
            Var: Indentation   = 0
            Const: IndentSize  = 3
            SyntaxArg: {spc}   = [{"[ \n]+"}]
            Inline: {indent+} := Indentation += IndentSize
            Inline: {indent-} := Indentation -= IndentSize
            Inline: {indent}  := " " * Indentation
         }{@Eval: Indentation = 0}

Criteria: 2
Comment: Strips all indentation and some spacing
Pass: 1

Criteria: 3
BackColor: Brown
Find: {"\n"}{" +"}
Replace: {nl}

Criteria: 4
BackColor: RoyalBlue
Find: {spc} "{" {spc}
Replace: {

Criteria: 5
BackColor: Pink
Find: {spc} "}"
Replace: }

Criteria: 6
BackColor: Purple
Find: {" *; *"}
Replace: ;

Criteria: 7
Find: {" *, *"}
Replace: ,{sp}

Criteria: 8
Comment: Handles semicolons
Pass: 2

Criteria: 9
Selected: True
BackColor: Green
Find: ({params~})
Replace: ({@Eval: Replace({Q}{params}{Q}, ";", "; ")})

Criteria: 10
BackColor: SandyBrown
Find: ;{char:"[^\n]"}
Replace: ;{nl}{char}

Criteria: 11
Comment: Performs indentation
Pass: 3

Criteria: 12
BackColor: DeepSkyBlue
Find: {nl}
Replace: {nl}{indent}

Criteria: 13
BackColor: DarkKhaki
Find: "{" [{comment: //{".*"}}]
Replace: {sp}{{comment: {comment}}{nl}{indent+}{indent}

Criteria: 14
BackColor: Yellow
Find: "}" [{comment: //{".*"}}]
Replace: {nl}{indent-}{indent}}{comment: {comment}}

Criteria: 15
BackColor: Brown
Find: {nl}{type:1}{func:1}([{args}])"{"
Replace: {nl}{type} {func}({args}){nl}{{indent+}{nl}{indent}

# End Search

# beautify.uc - uCalc Transformation file
# This file was saved with uCalc Transform 2.0 on 12/30/2013 1:43:30 PM
# Comment: Fixes indentation and spacing in C++ source code

ExternalKeywords: Exclude, Comment, Selected, ParentChild, FindMode, OutputFile, BatchAction, SEND
ExternalKeywords: Highlight, ForeColor, BackColor, FontName, FontSize, FontStyle
ExternalKeywords: FilterEndText, FilterSeparator, FilterSort, FilterSortFunc, FilterStartText, FilterUnique

FindMode: Replace

# Definitions

# Search Criteria

Criteria: 0
Enabled: True
Exclude: False
Comment: Fixes indentation and spacing in C++ source code
Selected: False
Highlight: False
ForeColor: ControlText
BackColor: Aqua
FontName:
FontSize:
FontStyle:
CaseSensitive: False
QuoteSensitive: True
CodeBlockSensitive: True
FilterEndText:
FilterSeparator: {#10}
FilterSort: False
FilterSortFunc:
FilterStartText:
FilterUnique: False
Min: 0
Max: -1
MinSoft: 0
MaxSoft: -1
BatchAction: Transform
OutputFile:
SEND:
StartAfter: 0
StopAfter: -1
SkipOver: False
ParentChild: 0
Pass: 0
PassOnce: True
Precedence: 0
RightToLeft: False

Criteria: 1
Find:
Replace: {@Define:
            Var: Indentation   = 0
            Const: IndentSize  = 3
            SyntaxArg: {spc}   = [{"[ \n]+"}]
            Inline: {indent+} := Indentation += IndentSize
            Inline: {indent-} := Indentation -= IndentSize
            Inline: {indent}  := " " * Indentation
         }{@Eval: Indentation = 0}

Criteria: 2
Comment: Strips all indentation and some spacing
Pass: 1

Criteria: 3
BackColor: Brown
Find: {"\n"}{" +"}
Replace: {nl}

Criteria: 4
BackColor: RoyalBlue
Find: {spc} "{" {spc}
Replace: {

Criteria: 5
BackColor: Pink
Find: {spc} "}"
Replace: }

Criteria: 6
BackColor: Purple
Find: {" *; *"}
Replace: ;

Criteria: 7
Selected: True
Find: {" *, *"}
Replace: ,{sp}

Criteria: 8
Comment: Handles semicolons
Pass: 2

Criteria: 9
BackColor: Green
Find: ({params~})
Replace: ({@Eval: Replace({#3}{params}{#3}, ";", "; ")})

Criteria: 10
BackColor: SandyBrown
Find: ;{char:"[^\n]"}
Replace: ;{nl}{char}

Criteria: 11
Comment: Performs indentation
Pass: 3

Criteria: 12
BackColor: DeepSkyBlue
Find: {nl}
Replace: {nl}{indent}

Criteria: 13
BackColor: DarkKhaki
Find: "{" [{comment: //{".*"}}]
Replace: {sp}{{comment: {comment}}{nl}{indent+}{indent}

Criteria: 14
BackColor: Yellow
Find: "}" [{comment: //{".*"}}]
Replace: {nl}{indent-}{indent}}{comment: {comment}}

Criteria: 15
BackColor: Brown
Find: {nl}{type:1}{func:1}([{args}])"{"
Replace: {nl}{type} {func}({args}){nl}{{indent+}{nl}{indent}

# End Search