Skip to content

Commit

Permalink
Fix a bug where Refund Fatigure/AP and Hyperactive is not triggered s…
Browse files Browse the repository at this point in the history
…ometimes

Nerf Refund AP.
  • Loading branch information
sogartar committed Mar 15, 2023
1 parent 7d80243 commit 9b24a19
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 139 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ Good on low-tier heavy armor against high armor penetration weapons like hammers
It is also viable with named Nimble gear.
* **Refund Action Points** - Unlocks the ability to refund all action points on a missed attack.
The skill can be used until the end of the turn.
The cost is 33% of the attack's fatigue + 1 per action point.
The cost is 40% of the attack's fatigue + 1.25 per action point.
The cost increases by 40% with each use in the same turn. On new turn the cost multiplier is reset.\
This perk fills the same niche as the Refund Fatigue perk, but also requires some fatigue to be useful.
* **Supple** - Gain a chance to have any attacker require two successful attack rolls in order to hit.
The chance starts from 135 and is reduced by 100% of maximum hitpoints and 150% of penalty to maximum fatigue.
Expand Down
1 change: 1 addition & 0 deletions gfx/metadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<sprite id="active_quirks_exertion" ic="FF624556" img="ui\actives\active_quirks_exertion.png" />
<sprite id="quirks_surprised_effect" ic="FF624556" img="ui\effects\quirks_surprised_effect.png" />
<sprite id="active_quirks_refund_action_points" ic="FF624556" img="ui\actives\active_quirks_refund_action_points.png" />
<sprite id="perk_quirks_refund_action_points" ic="FF624556" img="ui\perks\perk_quirks_refund_action_points.png" />
<sprite id="quirks_knackered_effect" ic="FF624556" img="ui\effects\quirks_knackered_effect.png" />
<sprite id="perk_quirks_slow_down" ic="FF624551" img="ui\perks\perk_quirks_slow_down.png" />
<sprite id="active_quirks_slow_down" ic="FF624552" img="ui\actives\active_quirks_slow_down.png" />
Expand Down
121 changes: 71 additions & 50 deletions scripts/!mods_preload/quirks.nut
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ local addPerkHyperactive = function() {
if (Math.round(fatigueRecoveryRateModifierPerSpentActionPoint) != fatigueRecoveryRateModifierPerSpentActionPoint) {
res += "\nRounding to the whole number is randomized with probability of rounding away from zero equal to the fraction part.";
}
res += "\nWill start the turn with at least 15 stamina if max fatigue allows it.";
return res;
};
gt.Const.Strings.PerkDescription.QuirksHyperactive <- gt.Quirks.getHyperactiveDescription(
Expand Down Expand Up @@ -189,74 +190,89 @@ local addPerkRefundFatigue = function() {
};
local addOnAfterSkillUsed = function() {
local onAfterSkillUsedExternal = function(_skill_container, _caller, _targetTile) {
if (!("m" in _skill_container)) {
return;
}
foreach(skill in _skill_container.m.Skills) {
skill.onAfterAnySkillUsed(_caller, _targetTile);
}
}
local onAfterSkillUsedInSkillContainer = function(_caller, _targetTile) {
onAfterSkillUsedExternal(this, _caller, _targetTile);
};
::mods_hookClass("skills/skill", function(c) {
local skillClass = ::libreuse.getParentClass(c, "skill");
if (skillClass == null) {
skillClass = c;
}
skillClass.onAfterSkillUsed <- function(_targetTile) {};
skillClass.onAfterAnySkillUsed <- function(_skill, _targetTile) {};
skillClass.onAfterAnySkillUsed <- function(_skill, _actor, _targetTile) {};
skillClass.m.IsOnAfterSkillUsedScheduled <- false;
skillClass.m.TargetTile <- null;
skillClass.m.IsUsedForFree <- false;
skillClass.isUsedForFree <- function() { return this.m.IsUsedForFree; };
# Copied for skill.use(...)
skillClass.quirksCanUse <- function(_targetTile, _forFree = false) {
if (!_forFree && !this.isAffordable() || !this.isUsable())
{
return false;
}
local user = this.m.Container.getActor();
if (!_forFree)
{
#this.logDebug(user.getName() + " uses skill " + this.getName());
}
if (this.isTargeted())
{
if (this.m.IsVisibleTileNeeded && !_targetTile.IsVisibleForEntity)
{
return false;
}
if (!this.onVerifyTarget(user.getTile(), _targetTile))
{
return false;
}
local d = user.getTile().getDistanceTo(_targetTile);
local levelDifference = user.getTile().Level - _targetTile.Level;
if (d < this.m.MinRange || !this.m.IsRanged && d > this.getMaxRange())
{
return false;
}
if (this.m.IsRanged && d > this.getMaxRange() + this.Math.min(this.m.MaxRangeBonus, this.Math.max(0, levelDifference)))
{
return false;
}
}
return true;
};
local useOriginal = skillClass.use;
skillClass.use = function(_targetTile, _forFree = false) {
this.m.IsOnAfterSkillUsedScheduled = false;
this.m.TargetTile = _targetTile;
this.m.IsUsedForFree = _forFree;
local container = this.getContainer();
// skill.use may remove this skill from the container,
// so we need to cache the actor in order to have
// access to it and the skill container after useOriginal.
local actor = this.getContainer().getActor();
local canUse = this.quirksCanUse(_targetTile, _forFree);
local ret = useOriginal(_targetTile, _forFree);
if (!ret || !this.isAttack()) {
this.m.IsOnAfterSkillUsedScheduled = true;
if (canUse || ret) {
this.onAfterSkillUsed(_targetTile);
container.onAfterSkillUsed(this, _targetTile);
actor.getSkills().onAfterSkillUsed(this, _targetTile);
}
return ret;
};
local onScheduledTargetHitOriginal = skillClass.onScheduledTargetHit;
skillClass.onScheduledTargetHit = function(_info) {
onScheduledTargetHitOriginal(_info);
if (!this.m.IsOnAfterSkillUsedScheduled) {
this.m.IsOnAfterSkillUsedScheduled = true;
this.Time.scheduleEvent(this.TimeUnit.Virtual, 1, this.onAfterSkillUsed, this.m.TargetTile);
local callbackData = {
container = this.getContainer(),
caller = this,
targetTile = this.m.TargetTile
};
this.Time.scheduleEvent(this.TimeUnit.Virtual, 1,
function(callbackData) {
if ("onAfterSkillUsed" in callbackData.container) {
callbackData.container.onAfterSkillUsed(callbackData.caller, callbackData.targetTile);
} else {
this.logWarning("onAfterSkillUsed not defined in skill_container. " +
"Hook to define it was probably not called. Falling back to call external definition.");
onAfterSkillUsedExternal(callbackData.container, callbackData.caller, callbackData.targetTile);
}
},
callbackData);
}
};
});
local onAfterSkillUsedInSkillContainer = function(_caller, _targetTile) {
onAfterSkillUsedExternal(this, _caller, _targetTile);
};
::mods_hookClass("skills/skill_container", function(c) {
c.onAfterSkillUsed <- onAfterSkillUsedInSkillContainer;
c.onAfterSkillUsed <- function(_caller, _targetTile) {
local actor = this.getActor();
foreach(skill in this.m.Skills) {
skill.onAfterAnySkillUsed(_caller, actor, _targetTile);
}
};
});
}
Expand Down Expand Up @@ -475,16 +491,21 @@ local addPerkSurprise = function() {
};
local addPerkRefundActionPoints = function() {
gt.Const.Quirks.RefundActionPointsAttackFatigueCostMult <- 0.3333;
gt.Const.Quirks.RefundActionPointsFatigueCostPerActionPoint <- 1.0;
gt.Const.Quirks.RefundActionPointsAttackFatigueCostMult <- 0.4;
gt.Const.Quirks.RefundActionPointsFatigueCostPerActionPoint <- 1.25;
gt.Const.Quirks.FatigueCostInSameTurnMult <- 1.4;
this.Const.Strings.PerkName.QuirksRefundActionPoints <- "Refund Action Points"
gt.Quirks.getRefundActionPointsDescription <- function(attackFatigueCostMult, fatigueCostPerActionPoint) {
gt.Quirks.getRefundActionPointsDescription <- function(attackFatigueCostMult, fatigueCostPerActionPoint, fatigueCostInSameTurnMult) {
return "Unlocks the ability to refund all action points on a missed attack. The skill can be used until the end of the turn. " +
"The cost is [color=" + this.Const.UI.Color.NegativeValue + "]" + this.Math.round(attackFatigueCostMult * 100) + "%[/color] of the attack's fatigue + " +
"[color=" + this.Const.UI.Color.NegativeValue + "]" + fatigueCostPerActionPoint + "[/color] per action point.";
"[color=" + this.Const.UI.Color.NegativeValue + "]" + fatigueCostPerActionPoint + "[/color] per action point. " +
"The cost increases by [color=" + this.Const.UI.Color.NegativeValue + "]" + this.Math.round((fatigueCostInSameTurnMult - 1) * 100) +
"%[/color] with each use in the same turn. On new turn the cost multiplier is reset.";
};
gt.Const.Strings.PerkDescription.QuirksRefundActionPoints <- gt.Quirks.getRefundActionPointsDescription(
gt.Const.Quirks.RefundActionPointsAttackFatigueCostMult, gt.Const.Quirks.RefundActionPointsFatigueCostPerActionPoint);
gt.Const.Quirks.RefundActionPointsAttackFatigueCostMult,
gt.Const.Quirks.RefundActionPointsFatigueCostPerActionPoint,
gt.Const.Quirks.FatigueCostInSameTurnMult);
local refundActionPointsPerkConsts = {
ID = "perk.quirks.refund_action_points",
Expand Down
4 changes: 4 additions & 0 deletions scripts/skills/actives/quirks_refund_action_points_skill.nut
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ this.quirks_refund_action_points_skill <- this.inherit("scripts/skills/skill", {
this.m.MaxRange = 0;
}

function setID(value) {
this.m.ID = value;
}

function setActionPontsRefund(value) {
this.m.ActionPontsRefund = value;
}
Expand Down
9 changes: 4 additions & 5 deletions scripts/skills/effects/quirks_exertion_effect.nut
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ this.quirks_exertion_effect <- this.inherit("scripts/skills/skill", {
+ "%[/color]\n" +
"Fatigue Cost [color=" + this.Const.UI.Color.NegativeValue + "]+" +
this.Math.round((this.getFatigueCostMult() - 1) * 100)
+ "%[/color]\n";
+ "%[/color]\n" +
"Note that due to limitations of the game mechanics the fatigue cost multiplier applies for all skills, not just attacks! " +
"This is why the efect is remove after using a skill.";
}

function getFatigueCostMult() {
Expand All @@ -41,16 +43,13 @@ this.quirks_exertion_effect <- this.inherit("scripts/skills/skill", {
container.getSkillByID("actives.shoot_stake") != null ||
container.getSkillByID("actives.fire_handgonne") != null ||
container.getSkillByID("effects.spearwall") != null) {
this.logInfo("exertion.onUpdate: not a valid weapon.");
return;
}
this.logInfo("exertion.onUpdate: applying effect.");
_properties.FatigueEffectMult *= this.getFatigueCostMult();
_properties.DamageTotalMult *= this.m.DamageMult;
}

function onAfterAnySkillUsed(_skill, _targetTile) {
this.logInfo("quirks_exertion_effect.onAfterAnySkillUsed");
function onAfterAnySkillUsed(_skill, _actor, _targetTile) {
if (_skill.getID() != "actives.quirks.exertion") {
this.removeSelf();
}
Expand Down
16 changes: 8 additions & 8 deletions scripts/skills/effects/quirks_plunge_effect.nut
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ this.quirks_plunge_effect <- this.inherit("scripts/skills/skill", {
if (_skill != null && _skill.isAttack() && !_skill.isRanged() &&
_targetEntity != null) {
_properties.DamageTotalMult *= this.getDamageMult(_skill);
this.m.IsSpent = true;
this.m.Skill = _skill;
this.m.TargetEntity = _targetEntity;
this.m.TargetEntityTile = _targetEntity.getTile();
Expand Down Expand Up @@ -155,13 +154,6 @@ this.quirks_plunge_effect <- this.inherit("scripts/skills/skill", {
this.removeSelf();
}

function onAfterAnySkillUsed(_skill, _targetTile) {
if (this.m.IsSpent) {
this.getContainer().getActor().setDirty(true);
this.getContainer().remove(this);
}
}

function onPlunge(_entity, _tag) {
local actorTile = _tag.User.getTile();
local plungeToTile = actorTile.hasNextTile(_tag.Direction) ? actorTile.getNextTile(_tag.Direction) : null;
Expand All @@ -174,11 +166,13 @@ this.quirks_plunge_effect <- this.inherit("scripts/skills/skill", {
if (_skill != this.m.Skill ||
_targetEntity.getCurrentProperties().IsImmuneToKnockBackAndGrab ||
_targetEntity.getCurrentProperties().IsRooted) {
this.removeSelf();
return;
}

local knockBackChance = this.getKnockBackChance(_skill);
if (knockBackChance * 1000 <= this.Math.rand(1, 1000)) {
this.removeSelf();
return;
}

Expand Down Expand Up @@ -211,5 +205,11 @@ this.quirks_plunge_effect <- this.inherit("scripts/skills/skill", {
this.onPlunge(null, tag);
}
}

this.removeSelf();
}

function onTargetMissed(_skill, _targetEntity) {
this.removeSelf();
}
});
43 changes: 30 additions & 13 deletions scripts/skills/perks/perk_quirks_hyperactive.nut
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ this.perk_quirks_hyperactive <- this.inherit("scripts/skills/skill", {
RefundApProbabilityPromile = this.Const.Quirks.HyperactiveRefundApProbabilityPromile,
FatigueRecoveryRateModifierPerSpentActionPoint = this.Const.Quirks.HyperactiveFatigueRecoveryRateModifierPerSpentActionPoint,
SpentActionPointsThisTurn = 0,
SpentActionPointsLastTurn = 0
SpentActionPointsLastTurn = 0,
},
function create() {
this.m.ID = "perk.quirks.hyperactive";
Expand All @@ -27,25 +27,42 @@ this.perk_quirks_hyperactive <- this.inherit("scripts/skills/skill", {
this.m.SpentActionPointsLastTurn * this.m.FatigueRecoveryRateModifierPerSpentActionPoint);
}

function onAfterAnySkillUsed(_skill, _targetTile) {
if (_skill == null || _skill.isUsedForFree()) {
return;
function getSkillApCost(skill, actor) {
if (actor.getCurrentProperties().IsSkillUseFree)
{
return 0;
}
else if (actor.getCurrentProperties().IsSkillUseHalfCost)
{
return this.Math.max(1, this.Math.floor(skill.m.ActionPointCost / 2));
}
else
{
return skill.m.ActionPointCost;
}
}

this.m.SpentActionPointsThisTurn += _skill.getActionPointCost();
this.logInfo("perk_quirks_hyperactive.onAfterAnySkillUsed this.m.SpentActionPointsThisTurn = " + this.m.SpentActionPointsThisTurn);

function onAfterAnySkillUsed(_skill, _actor, _targetTile) {
local ap = this.getSkillApCost(_skill, _actor);
if (ap == 0) {
return;
}
this.m.SpentActionPointsThisTurn += ap;
if (this.Math.rand(1, 1000) <= this.m.RefundApProbabilityPromile) {
local actor = this.getContainer().getActor();
actor.setActionPoints(this.Math.min(actor.getActionPointsMax(),
actor.getActionPoints() + _skill.getActionPointCost()));
actor.setDirty(true);
this.spawnIcon("perk_quirks_hyperactive", actor.getTile());
_actor.setActionPoints(this.Math.min(_actor.getActionPointsMax(),
_actor.getActionPoints() + ap));
local spwanIconArg = {
actor = _actor
};
local spwanIcon = function(_arg) {
this.spawnIcon("perk_quirks_hyperactive", _arg.actor.getTile());
}
this.Time.scheduleEvent(this.TimeUnit.Virtual, 200, spwanIcon.bindenv(this), spwanIconArg);
_actor.setDirty(true);
}
}

function onTurnEnd() {
this.logInfo("perk_quirks_hyperactive.onTurnEnd this.m.SpentActionPointsThisTurn = " + this.m.SpentActionPointsThisTurn);
this.m.SpentActionPointsLastTurn = this.m.SpentActionPointsThisTurn;
this.m.SpentActionPointsThisTurn = 0;
}
Expand Down
Loading

0 comments on commit 9b24a19

Please sign in to comment.