Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added BaseLib/images/ui/temp_hp.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions BaseLib/localization/deu/card_keywords.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"BASELIB-PURGE.title": "Tilgen",
"BASELIB-PURGE.description": "Wird dauerhaft aus dem Kampf und deinem Deck entfernt.",
"BASELIB-TEMP_HP.title": "Temporäre LP",
"BASELIB-TEMP_HP.description": "Absorbiert Schaden, bevor LP verloren gehen – auch Schaden, der [gold]Block[/gold] ignoriert. Verschwindet am Ende des Kampfes."
"BASELIB-PURGE.description": "Wird dauerhaft aus dem Kampf und deinem [gold]Deck[/gold] entfernt."
}
5 changes: 4 additions & 1 deletion BaseLib/localization/deu/powers.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@
"BASELIB-CUSTOM_TEMPORARY_POWER_MODEL.UP.description": "Erhöht einen [gold]Effekt[/gold] für eine gewisse Zeit.",
"BASELIB-CUSTOM_TEMPORARY_POWER_MODEL.UP.smartDescription": "Erhalte [blue]{Amount:abs()}[/blue] [gold]{TemporaryPowerTitle}[/gold] bis zum Ende {UntilEndOfOtherSideTurn:cond:dieses Zuges|des gegnerischen Zuges}.{Repeat:cond:>0?\\nHält [blue]{Repeat:diff()}[/blue] {Repeat:plural:weiteren Zug|weitere Züge} an.|}",
"BASELIB-CUSTOM_TEMPORARY_POWER_MODEL.DOWN.description": "Verringert einen [gold]Effekt[/gold] für eine gewisse Zeit.",
"BASELIB-CUSTOM_TEMPORARY_POWER_MODEL.DOWN.smartDescription": "Verliere [blue]{Amount:abs()}[/blue] [gold]{TemporaryPowerTitle}[/gold] bis zum Ende {UntilEndOfOtherSideTurn:cond:dieses Zuges|des gegnerischen Zuges}.{Repeat:cond:>0?\\nHält [blue]{Repeat:diff()}[/blue] {Repeat:plural:weiteren Zug|weitere Züge} an.|}"
"BASELIB-CUSTOM_TEMPORARY_POWER_MODEL.DOWN.smartDescription": "Verliere [blue]{Amount:abs()}[/blue] [gold]{TemporaryPowerTitle}[/gold] bis zum Ende {UntilEndOfOtherSideTurn:cond:dieses Zuges|des gegnerischen Zuges}.{Repeat:cond:>0?\\nHält [blue]{Repeat:diff()}[/blue] {Repeat:plural:weiteren Zug|weitere Züge} an.|}",
"BASELIB-TEMP_HP_POWER.title": "Temporäre LP",
"BASELIB-TEMP_HP_POWER.description": "Absorbiert Schaden, bevor LP verloren gehen – auch Schaden, der [gold]Block[/gold] ignoriert. Verschwindet am Ende des Kampfes.",
"BASELIB-TEMP_HP_POWER.smartDescription": "Absorbiert Schaden, bevor LP verloren gehen – auch Schaden, der [gold]Block[/gold] ignoriert. Verschwindet am Ende des Kampfes."
}
3 changes: 3 additions & 0 deletions BaseLib/localization/deu/static_hover_tips.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
"BASELIB-EXHAUSTIVE.description": "Diese Karte wird nach [blue]X[/blue] Verwendungen [gold]Erschöpft[/gold].",
"BASELIB-EXHAUSTIVE.smartDescription": "Diese Karte wird nach {Exhaustive:cond:>1?[blue]{Exhaustive}[/blue] |einer }{Exhaustive:plural:Verwendung|Verwendungen} [gold]Erschöpft[/gold].",
"BASELIB-REFUND.title": "Erstattung",
"BASELIB-REFUND.description": "Wird Energie für diese Karte ausgegeben, werden bis zu [blue]{Refund}[/blue] dieser Energie zurückerstattet.",
"BASELIB-TEMP_HP.title": "Temporäre LP",
"BASELIB-TEMP_HP.description": "Absorbiert Schaden, bevor LP verloren gehen – auch Schaden, der [gold]Block[/gold] ignoriert. Verschwindet am Ende des Kampfes.",
"BASELIB-REFUND.description": "Wird Energie für diese Karte ausgegeben, werden bis zu [blue]X[/blue] dieser Energie zurückerstattet.",
"BASELIB-REFUND.smartDescription": "Wird Energie für diese Karte ausgegeben, werden bis zu [blue]{Refund}[/blue] dieser Energie zurückerstattet.",
"BASELIB-SCRY.title": "Spähen",
Expand Down
4 changes: 3 additions & 1 deletion BaseLib/localization/eng/card_keywords.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{
"BASELIB-PURGE.title": "Purge",
"BASELIB-PURGE.description": "Removed from combat and your [gold]Deck[/gold] permanently."
"BASELIB-PURGE.description": "Removed from combat and your [gold]Deck[/gold] permanently.",
"BASELIB-TEMP_HP.title": "Temporary HP",
"BASELIB-TEMP_HP.description": "Absorbs damage before HP is lost, even damage that ignores [gold]Block[/gold]. Disappears at the end of combat."
}
5 changes: 4 additions & 1 deletion BaseLib/localization/eng/powers.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@
"BASELIB-CUSTOM_TEMPORARY_POWER_MODEL.UP.description": "Increase a [gold]Power[/gold] for some time.",
"BASELIB-CUSTOM_TEMPORARY_POWER_MODEL.UP.smartDescription": "Gain [blue]{Amount:abs()}[/blue] [gold]{TemporaryPowerTitle}[/gold] until the end of your {UntilEndOfOtherSideTurn:cond:|enemies }turn.{Repeat:cond:>0?\\nLasts for [blue]{Repeat:diff()}[/blue] additional {Repeat:plural:turn|turns}.|}",
"BASELIB-CUSTOM_TEMPORARY_POWER_MODEL.DOWN.description": "Decrease a [gold]Power[/gold] for some time.",
"BASELIB-CUSTOM_TEMPORARY_POWER_MODEL.DOWN.smartDescription": "Lose [blue]{Amount:abs()}[/blue] [gold]{TemporaryPowerTitle}[/gold] until the end of your {UntilEndOfOtherSideTurn:cond:|enemies }turn.{Repeat:cond:>0?\\nLasts for [blue]{Repeat:diff()}[/blue] additional {Repeat:plural:turn|turns}.|}"
"BASELIB-CUSTOM_TEMPORARY_POWER_MODEL.DOWN.smartDescription": "Lose [blue]{Amount:abs()}[/blue] [gold]{TemporaryPowerTitle}[/gold] until the end of your {UntilEndOfOtherSideTurn:cond:|enemies }turn.{Repeat:cond:>0?\\nLasts for [blue]{Repeat:diff()}[/blue] additional {Repeat:plural:turn|turns}.|}",
"BASELIB-TEMP_HP_POWER.title": "Temporary HP",
"BASELIB-TEMP_HP_POWER.description": "Absorbs damage before HP is lost, even damage that ignores [gold]Block[/gold]. Disappears at the end of combat.",
"BASELIB-TEMP_HP_POWER.smartDescription": "Absorbs damage before HP is lost, even damage that ignores [gold]Block[/gold]. Disappears at the end of combat."
}
3 changes: 3 additions & 0 deletions BaseLib/localization/eng/static_hover_tips.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
"BASELIB-EXHAUSTIVE.description": "This card [gold]Exhausts[/gold] after [blue]X[/blue] uses.",
"BASELIB-EXHAUSTIVE.smartDescription": "This card [gold]Exhausts[/gold] after {Exhaustive:cond:>1?[blue]{Exhaustive}[/blue] |}{Exhaustive:plural:use|uses}.",
"BASELIB-REFUND.title": "Refund",
"BASELIB-REFUND.description": "When energy is spent on this card, up to [blue]{Refund}[/blue] of that energy is refunded.",
"BASELIB-TEMP_HP.title": "Temporary HP",
"BASELIB-TEMP_HP.description": "Absorbs damage before HP is lost, even damage that ignores [gold]Block[/gold]. Disappears at the end of combat.",
"BASELIB-REFUND.description": "When energy is spent on this card, up to [blue]X[/blue] of that energy is refunded.",
"BASELIB-REFUND.smartDescription": "When energy is spent on this card, up to [blue]{Refund}[/blue] of that energy is refunded.",
"BASELIB-SCRY.title": "Scry",
Expand Down
2 changes: 2 additions & 0 deletions BaseLib/localization/rus/card_keywords.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{
"BASELIB-TEMP_HP.title": "Временное здоровье",
"BASELIB-TEMP_HP.description": "Поглощает урон до потери здоровья, даже урон, игнорирующий [gold]блок[/gold]. Исчезает в конце боя.",
"BASELIB-PURGE.title": "Исчищается",
"BASELIB-PURGE.description": "Эта карта исчезает из игры и удаляется из [gold]колоды[/gold]."
}
5 changes: 4 additions & 1 deletion BaseLib/localization/rus/powers.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@
"BASELIB-CUSTOM_TEMPORARY_POWER_MODEL.UP.description": "Временно усиливает эффект [gold]таланта[/gold].",
"BASELIB-CUSTOM_TEMPORARY_POWER_MODEL.UP.smartDescription": "Дает [blue]{Amount:abs()}[/blue] [gold]{TemporaryPowerTitle}[/gold] до конца хода{UntilEndOfOtherSideTurn:cond:| врага}.{Repeat:cond:>0?\\nДействует еще [blue]{Repeat:diff()}[/blue] доп. {Repeat:plural(ru):ход|хода|ходов}.|}",
"BASELIB-CUSTOM_TEMPORARY_POWER_MODEL.DOWN.description": "Временно ослабляет эффект [gold]таланта[/gold].",
"BASELIB-CUSTOM_TEMPORARY_POWER_MODEL.DOWN.smartDescription": "Вы теряете [blue]{Amount:abs()}[/blue] [gold]{TemporaryPowerTitle}[/gold] до конца хода {UntilEndOfOtherSideTurn:cond:| врага}.{Repeat:cond:>0?\\nДействует еще [blue]{Repeat:diff()}[/blue] доп. {Repeat:plural(ru):ход|хода|ходов}.|}"
"BASELIB-CUSTOM_TEMPORARY_POWER_MODEL.DOWN.smartDescription": "Вы теряете [blue]{Amount:abs()}[/blue] [gold]{TemporaryPowerTitle}[/gold] до конца хода {UntilEndOfOtherSideTurn:cond:| врага}.{Repeat:cond:>0?\\nДействует еще [blue]{Repeat:diff()}[/blue] доп. {Repeat:plural(ru):ход|хода|ходов}.|}",
"BASELIB-TEMP_HP_POWER.title": "Временное здоровье",
"BASELIB-TEMP_HP_POWER.description": "Поглощает урон до потери здоровья, даже урон, игнорирующий [gold]блок[/gold]. Исчезает в конце боя.",
"BASELIB-TEMP_HP_POWER.smartDescription": "Поглощает урон до потери здоровья, даже урон, игнорирующий [gold]блок[/gold]. Исчезает в конце боя."
}
3 changes: 3 additions & 0 deletions BaseLib/localization/rus/static_hover_tips.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
"BASELIB-EXHAUSTIVE.description": "Эта карта [gold]сжигается[/gold] после того, как будет разыграна еще [blue]X[/blue] раз.",
"BASELIB-EXHAUSTIVE.smartDescription": "Эта карта [gold]сжигается[/gold] после того, как будет разыграна еще {Exhaustive:cond:>1?[blue]{Exhaustive}[/blue] |}{Exhaustive:plural(ru):раз|раза|раз}.",
"BASELIB-REFUND.title": "Возврат",
"BASELIB-REFUND.description": "Когда вы тратите энергию при разыгрывании этой карты, возвращает до [blue]{Refund}[/blue] {energyPrefix:energyIcons(1)} назад.",
"BASELIB-TEMP_HP.title": "Временное здоровье",
"BASELIB-TEMP_HP.description": "Поглощает урон до потери здоровья, даже урон, игнорирующий [gold]блок[/gold]. Исчезает в конце боя.",
"BASELIB-REFUND.description": "Когда вы тратите энергию при разыгрывании этой карты, возвращает до [blue]X[/blue] энергии назад.",
"BASELIB-REFUND.smartDescription": "Когда вы тратите энергию при разыгрывании этой карты, возвращает до [blue]{Refund}[/blue] {energyPrefix:energyIcons(1)} назад.",
"BASELIB-SCRY.title": "Предсказывание карт",
Expand Down
4 changes: 3 additions & 1 deletion BaseLib/localization/zhs/card_keywords.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{
"BASELIB-PURGE.title": "即逝",
"BASELIB-PURGE.description": "从战斗和你的[gold]牌组[/gold]中永久移除。"
"BASELIB-PURGE.description": "从战斗和你的[gold]牌组[/gold]中永久移除。",
"BASELIB-TEMP_HP.title": "临时生命",
"BASELIB-TEMP_HP.description": "在损失生命值之前吸收伤害,即使是无视[gold]格挡[/gold]的伤害。战斗结束时消失。"
}
5 changes: 4 additions & 1 deletion BaseLib/localization/zhs/powers.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@
"BASELIB-CUSTOM_TEMPORARY_POWER_MODEL.UP.description": "在一段时间内提升[gold]能力[/gold]。",
"BASELIB-CUSTOM_TEMPORARY_POWER_MODEL.UP.smartDescription": "获得[blue]{Amount:abs()}[/blue]层[gold]{TemporaryPowerTitle}[/gold],持续到你的{UntilEndOfOtherSideTurn:cond:|敌人}回合结束。{Repeat:cond:>0?\\n额外持续[blue]{Repeat:diff()}[/blue]个回合。|}",
"BASELIB-CUSTOM_TEMPORARY_POWER_MODEL.DOWN.description": "在一段时间内降低[gold]能力[/gold]。",
"BASELIB-CUSTOM_TEMPORARY_POWER_MODEL.DOWN.smartDescription": "失去[blue]{Amount:abs()}[/blue]层[gold]{TemporaryPowerTitle}[/gold],持续到你的{UntilEndOfOtherSideTurn:cond:|敌人}回合结束。{Repeat:cond:>0?\\n额外持续[blue]{Repeat:diff()}[/blue]个回合。|}"
"BASELIB-CUSTOM_TEMPORARY_POWER_MODEL.DOWN.smartDescription": "失去[blue]{Amount:abs()}[/blue]层[gold]{TemporaryPowerTitle}[/gold],持续到你的{UntilEndOfOtherSideTurn:cond:|敌人}回合结束。{Repeat:cond:>0?\\n额外持续[blue]{Repeat:diff()}[/blue]个回合。|}",
"BASELIB-TEMP_HP_POWER.title": "临时生命",
"BASELIB-TEMP_HP_POWER.description": "在损失生命值之前吸收伤害,即使是无视[gold]格挡[/gold]的伤害。战斗结束时消失。",
"BASELIB-TEMP_HP_POWER.smartDescription": "在损失生命值之前吸收伤害,即使是无视[gold]格挡[/gold]的伤害。战斗结束时消失。"
}
5 changes: 4 additions & 1 deletion BaseLib/localization/zhs/static_hover_tips.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
"BASELIB-EXHAUSTIVE.description": "这张牌在被使用 [blue]X[/blue] 次后会[gold]消耗[/gold]。",
"BASELIB-EXHAUSTIVE.smartDescription": "这张牌在被使用 {Exhaustive:cond:>1?[blue]{Exhaustive}[/blue] |}次后会[gold]消耗[/gold]。",
"BASELIB-REFUND.title": "回能",
"BASELIB-REFUND.description": "当此牌花费能量时,返还最多 [blue]{Refund}[/blue] 点能量。",
"BASELIB-TEMP_HP.title": "临时生命",
"BASELIB-TEMP_HP.description": "在损失生命值之前吸收伤害,即使是无视[gold]格挡[/gold]的伤害。战斗结束时消失。",
"BASELIB-REFUND.description": "当此牌花费能量时,返还最多 [blue]X[/blue] 点能量。",
"BASELIB-REFUND.smartDescription": "当此牌花费能量时,返还最多 [blue]{Refund}[/blue] 点能量。",
"BASELIB-SCRY.title": "预见",
"BASELIB-SCRY.description": "检视你[gold]抽牌堆[/gold]顶部的 [blue]X[/blue] 张牌。你可以选择丢弃其中的任意张牌。",
"BASELIB-SCRY.smartDescription": "检视你[gold]抽牌堆[/gold]顶部的{Scry:cond:>1?[blue]{Scry:diff()}[/blue]张牌|一张牌}。你可以{Scry:cond:>1?选择丢弃其中的任意张牌|选择丢弃这张牌}。"
}
}
6 changes: 6 additions & 0 deletions Cards/BaseLibKeywords.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,10 @@ public class BaseLibKeywords
/// A card that removes itself from the deck when played.
/// </summary>
[CustomEnum] [KeywordProperties(AutoKeywordPosition.After)] public static CardKeyword Purge;

/// See TempHpPower and TempHpCmd
/// <summary>
/// Temporary HP absorbs damage before HP and disappears at the end of combat.
/// </summary>
[CustomEnum("Temp_HP")] [KeywordProperties(AutoKeywordPosition.After)] public static CardKeyword TempHp;
}
119 changes: 119 additions & 0 deletions Commands/TempHpCmd.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
using BaseLib.Patches.Content;
using BaseLib.Powers;
using MegaCrit.Sts2.Core.Combat;
using MegaCrit.Sts2.Core.Commands;
using MegaCrit.Sts2.Core.Entities.Creatures;
using MegaCrit.Sts2.Core.GameActions.Multiplayer;
using MegaCrit.Sts2.Core.Helpers;
using MegaCrit.Sts2.Core.HoverTips;
using MegaCrit.Sts2.Core.Models;
using MegaCrit.Sts2.Core.Nodes;
using MegaCrit.Sts2.Core.Nodes.Vfx;
using MegaCrit.Sts2.Core.ValueProps;
using MegaCrit.Sts2.addons.mega_text;

namespace BaseLib.Commands;

/// <summary>
/// Public API for the Temporary HP mechanic (port of StSLib's Temporary HP).
/// Temporary HP absorbs any HP loss that goes through the damage pipeline (attacks, Poison, self-damage, ...).
/// Order: Block → Osty redirect → Temporary HP → late-pass buffs (ModifyHpLostAfterOstyLate, e.g.
/// Buffer-style prevention) → real HP. Disappears at the end of combat.
/// See <see cref="TempHpPower" /> for the underlying hidden power and <see cref="Hooks.ITempHpLossModifier" />
/// for the per-hit modification hook.
/// </summary>
public static class TempHpCmd
{
/// <summary>
/// Reserved damage-props bit: damage carrying this flag bypasses Temporary HP entirely
/// (the StSLib "ignoresTempHP" equivalent). Vanilla only uses bits 1-4; BaseLib reserves bit 28.
/// </summary>
public const ValueProp IgnoresTempHp = (ValueProp)(1 << 28);

/// <summary>Hover tip shown when a creature has Temporary HP. Usable on cards via WithTip(TempHpCmd.TempHp).</summary>
[CustomEnum] public static StaticHoverTip TempHp;

/// <summary>Fired after a creature gains Temporary HP: (creature, amount gained, new total).</summary>
public static event Action<Creature, int, int>? Gained;

/// <summary>
/// Fired after a creature loses Temporary HP: (creature, amount lost, new total, damage that caused it).
/// The DamageResult is null for non-damage losses (RemoveAll, foreign PowerCmd decrements, death flush).
/// </summary>
public static event Action<Creature, int, int, DamageResult?>? Lost;

/// <summary>
/// Grant Temporary HP to a creature. Stacks with existing Temporary HP.
/// Multiplayer-safe when called from synchronized contexts (card OnPlay, hooks, networked console commands),
/// exactly like any power application.
/// </summary>
/// <returns>The creature's new Temporary HP total.</returns>
public static async Task<int> Add(PlayerChoiceContext choiceContext, Creature target, int amount,
Creature? applier = null, CardModel? cardSource = null, bool showVfx = true)
{
if (amount <= 0)
return Get(target);
var power = await PowerCmd.Apply<TempHpPower>(choiceContext, target, amount, applier, cardSource, silent: true);
if (power != null && showVfx)
PlayGainFx(target);
return power?.Amount ?? Get(target);
}

/// <summary>Remove all Temporary HP from a creature (the StSLib RemoveAllTemporaryHPAction equivalent).</summary>
public static async Task RemoveAll(Creature target)
{
var power = target.GetPower<TempHpPower>();
if (power == null)
return;
var had = power.Amount;
await PowerCmd.Remove(power);
if (had > 0)
InvokeLost(target, had, 0, null);
}

/// <summary>The creature's current Temporary HP (0 if none).</summary>
public static int Get(Creature target)
{
return target.GetPower<TempHpPower>()?.Amount ?? 0;
}

internal static void InvokeGained(Creature creature, int gained, int total)
{
Gained?.Invoke(creature, gained, total);
}

internal static void InvokeLost(Creature creature, int lost, int total, DamageResult? source)
{
Lost?.Invoke(creature, lost, total, source);
}

private static void PlayGainFx(Creature target)
{
if (!CombatManager.Instance.IsInProgress)
return;
SfxCmd.Play("event:/sfx/heal");
VfxCmd.PlayOnCreatureCenter(target, "vfx/vfx_cross_heal");
}

/// <summary>
/// Gold floating number for absorbed damage — the only feedback on a fully absorbed hit, since vanilla
/// shows no damage number and no hurt anim when the final HP loss is 0.
/// </summary>
internal static void PlayAbsorbFx(Creature target, int absorbed)
{
if (absorbed <= 0 || !CombatManager.Instance.IsInProgress)
return;
var vfx = NDamageNumVfx.Create(target, absorbed);
if (vfx == null)
return;
vfx.Modulate = StsColors.gold; // the _Ready tween animates modulate gold -> cream, mimicking vanilla's red -> cream
var label = vfx.GetNodeOrNull<MegaLabel>("Label");
label?.AddThemeColorOverride("font_color", StsColors.gold);
label?.AddThemeColorOverride("font_outline_color", StsColors.rewardLabelGoldOutline);
var container = target.GetVfxContainer();
if (container != null)
container.AddChildSafely(vfx);
else
NRun.Instance?.GlobalUi.AddChildSafely(vfx);
}
}
Loading