MSUTeam / MSU

Modding Standards and Utilities for Battle Brothers
22 stars 4 forks source link

Feat affordability preview improved #346

Closed LordMidas closed 5 months ago

LordMidas commented 6 months ago

This contains two deprecation commits. The feature of this PR is in the main feat commit, so you may wanna look into the diff of that commit only to get an overview of how it works.

I would like to deprecate the scheduled changes system of skills because, while it is an interesting idea, I think there are plenty of ways to work around it if you design/implement skills in a smart way. Personally I have never used it, and I believe it is a bloat to support further.

This is a massive improvement over the current Affordability Preview system we have in MSU which I implemented in #88. That system is convoluted to work with from the user's perspective and is also limited in certain aspects. The new system is far more intuitive and in fact covers some edge cases which the old system failed in.

Old System (the one this PR deprecates):

New System:

Example: Let's say I have a skill which after at least 2 tiles reduces the AP cost of all attacks by 1. But this effect expires when you use any skill.

Old System usage:

function onAfterUpdate( _properties )
{
    if (this.getContainer().getActor().getTile().getDistanceTo(this.m.StartingTile) >= 2)
    {
        foreach (skill in this.getContainer().getAllSkillsOfType(::Const.SkillType.Active))
        {
            if (skill.isAttack() && skill.m.ActionPointCost > 1) // Notice we only want to decrease the AP cost if it is 2 or more
                skill.m.ActionPointCost -= 1;
        }
    }
}

function onAffordablePreview( _skill, _movementTile )
{
    // The character is previewing the usage of a skill, so we want to remove the AP cost reduction
    if (_skill != null)
    {
        foreach (skill in this.getContainer().getAllSkillsOfType(::Const.SkillType.Active))
        {           
            this.modifyPreviewField(skill, "ActionPointCost", 0, false);
        }
    }
    // The character is previewing a movement, so we want to apply the AP cost reduction if applicable
    else if (_movementTile != null)
    {
        // If we have already traveled at least 2 tiles this turn, we don't want to reduce the AP cost further
        if (this.getContainer().getActor().getTile().getDistanceTo(this.m.StartingTile) >= 2)
            return;

        if (_movementTile.getDistanceTo(this.getContainer().getActor().getTile()) >= 2)
        {
            foreach (skill in this.getContainer().getAllSkillsOfType(::Const.SkillType.Active))
            {   
                // Notice there is no way for us to first check here if the skill's AP cost         
                // is actually 2 or more at this point in the update cycle, because this onAffordablePreview function
                // doesn't work like that
            if (skill.isAttack())
                this.modifyPreviewField(skill, "ActionPointCost", -1 , false);
            }
        }
    }
}

New System:

function onAfterUpdate( _properties )
{
    if (this.getContainer().getActor().getTile().getDistanceTo(this.m.StartingTile) >= 2)
    {
        foreach (skill in this.getContainer().getAllSkillsOfType(::Const.SkillType.Active))
        {
            if (skill.isAttack() && skill.m.ActionPointCost > 1) // Notice we only want to decrease the AP cost if it is 2 or more
                skill.m.ActionPointCost -= 1;
        }
    }
}

function onAfterUpdatePreview( _properties, _previewedSkill, _previewedMovement )
{
    // The character is previewing the usage of a skill
    if (_previewedSkill != null)
    {
        // Do nothing i.e. apply no bonus from this perk
    }
    // The character is previewing a movement, so we want to apply the AP cost reduction if applicable
    else if (_previewedMovement != null)
    {
        // If we have already traveled at least 2 tiles this turn, we don't want to reduce the AP cost further
        if (this.getContainer().getActor().getTile().getDistanceTo(this.m.StartingTile) >= 2)
            return;

        if (_previewedMovement.End.getDistanceTo(this.getContainer().getActor().getTile()) >= 2)
        {
            foreach (skill in this.getContainer().getAllSkillsOfType(::Const.SkillType.Active))
            {   
                // Because this function runs in the same order as onAfterUpdate, we can actually check for the
                // AP cost of the skill at this point (in addition to the "preview" changes from other skills) which is fantastic!
                if (skill.isAttack() && skill.m.ActionPointCost > 1)
                    skill.m.ActionPointCost -= 1;
            }
        }
    }
}
LordMidas commented 6 months ago

One thing that I need Taro/Enduriel to improve in this system is that if the affordability preview would make a skill that is currently unaffordable affordable again, then we need to convey this information somehow. Currently, that skill's icon stays black&white because the vanilla affordability preview system only considers the case of currently affordable skills potentially becoming unaffordable and hence puts a "prohibited" icon on them. It does not expect currently unaffordable skills to become affordable. An idea for this case could be to put a green check mark on such skills to counter the red prohibited sign.

Relevant functions for achieving this are:

// in `turnsequencebar_module.js`:
TacticalScreenTurnSequenceBarModule.prototype.addSkillToList
TacticalScreenTurnSequenceBarModule.prototype.updateEntitySkillsPreview

// in `turn_sequence_bar.nut:`
setActiveEntityCostsPreview
resetActiveEntityCostsPreview
LordMidas commented 5 months ago

Closing in favor of #354