X2CommunityCore / X2WOTCCommunityHighlander

https://steamcommunity.com/workshop/filedetails/?id=1134256495
MIT License
60 stars 68 forks source link

Investigate how hack chances work #1222

Open Iridar opened 1 year ago

Iridar commented 1 year ago

This is more of a memo to myself. Currently I have no idea how hack chances are calculated, and there are some very opinionated individuals that say they're bogus. With other areas of game's code being less than perfect, it's reasonable to assume the hack chance code could have bugs too.

BlackDog86 commented 10 months ago

Most of this looks to be handled in X2AbilityToHitCalc_Hacking:

class X2AbilityToHitCalc_Hacking extends X2AbilityToHitCalc config(GameCore);

var config int SKULLJACK_HACKING_BONUS;

var bool bAlwaysSucceed;

function RollForAbilityHit(XComGameState_Ability kAbility, AvailableTarget kTarget, out AbilityResultContext ResultContext)
{
    local XComGameStateHistory History;
    local XComGameState_Unit Hacker;
    local XComGameState_BaseObject TargetState;

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

    if (Hacker != None && TargetState != None)
    {
        ResultContext.StatContestResult = `SYNC_RAND(100);
        if( `CHEATMGR.bDeadEye || bAlwaysSucceed )
        {
            ResultContext.StatContestResult = 0;
        }
        ResultContext.HitResult = eHit_Success;

        `COMBATLOG(Hacker.GetName(eNameType_RankFull) @ "uses hack. Rolls:" @ ResultContext.StatContestResult $ "%" @ ResultContext.HitResult);
    }
    else
    {
        `COMBATLOG("Hack failed due to no Hacker or no Target!");
        ResultContext.HitResult = eHit_Miss;
    }
}

static function int GetHackAttackForUnit(XComGameState_Unit Hacker, XComGameState_Ability AbilityState)
{
    local XComGameState_Item SourceWeapon;
    local X2GremlinTemplate GremlinTemplate;
    local int HackAttack;
    local bool IncludeHackBonus;

    HackAttack = Hacker.GetCurrentStat(eStat_Hacking);

    // when the skullmining tech is researched, carrying the skulljack confers a bonus to hacking
    IncludeHackBonus = `XCOMHQ.IsTechResearched('Skullmining') || class'X2TacticalGameRulesetDataStructures'.static.TacticalOnlyGameMode( );
    if( Hacker.HasItemOfTemplateType('SKULLJACK') && IncludeHackBonus )
    {
        HackAttack += default.SKULLJACK_HACKING_BONUS;
    }

    SourceWeapon = AbilityState.GetSourceWeapon();
    if (SourceWeapon != None)
    {
        GremlinTemplate = X2GremlinTemplate(SourceWeapon.GetMyTemplate());
        if (GremlinTemplate != None)
            HackAttack += GremlinTemplate.HackingAttemptBonus;
    }

    return HackAttack;
}

static function int GetHackDefenseForTarget(XComGameState_BaseObject TargetState)
{
    local XComGameState_InteractiveObject ObjectState;
    local XComGameState_Unit UnitState;

    ObjectState = XComGameState_InteractiveObject(TargetState);
    if (ObjectState != None)
    {
        return ObjectState.LockStrength;
    }
    UnitState = XComGameState_Unit(TargetState);
    if (UnitState != None)
    {
        return UnitState.GetCurrentStat(eStat_HackDefense);
    }

    return -1;
}

//  Hacking hit chance is displayed in a special UI and does not  use this functionality. -jbouscher
protected function int GetHitChance(XComGameState_Ability kAbility, AvailableTarget kTarget, optional out ShotBreakdown m_ShotBreakdown, optional bool bDebugLog = false)
{
    m_ShotBreakdown.HideShotBreakdown = true;
    return 0;
}
BlackDog86 commented 10 months ago

Looks as though the actual hit rolls side of it is pretty sane but what the UI is displaying may well be different, UIHackingScreen is a pretty long winded file!