Targeted item actions (action/heal), item.add scenario action, inventory cell Decorate; mod cover

This commit is contained in:
cthangover committed 2026-10-22 17:09:39 +02:00
1 parent d560638501
commit 468e393550
9 files changed
+313 -1

No files matched your search

+43
View File
@@ -0,0 +1,43 @@
using System;
using Cthangover.Core.Items;
using Cthangover.Core.Settings;
namespace Cthangover.Mods.Items
{
/// <summary>
/// Restores health of the chosen party member (<c>"ItemAction": "action/heal"</c>).
/// The amount is item data, not code: the item's <c>"heal"</c> key (an extra JSON
/// key, so it lands in <see cref="IItem.Properties"/>), which lets one action serve
/// a mushroom and an elixir alike. Health never rises above its base value, and a
/// fallen character (health 0) is not revived — that is a different effect.
/// The party member's attributes are the very objects the battle uses, so a heal
/// in the inventory during a battle shows on the battle widgets at once.
/// </summary>
public class HealItemAction : ITargetedItemAction
{
/// <summary>Item property with the amount of health restored.</summary>
public const string HealProperty = "heal";
/// <summary>Action id referenced by item definitions.</summary>
public string ID => "action/heal";
/// <summary>Healing always needs a target; without one nothing happens.</summary>
public bool UseAction(IItem item) => false;
/// <summary>
/// Adds the item's <c>heal</c> amount to the target's health, capped at the
/// base value. Returns <c>false</c> (item not spent) for a missing target, a
/// non-positive amount, a fallen target or one already at full health.
/// </summary>
public bool UseOn(IItem item, CharacterInfoData target)
{
var health = target?.Attributes?.Health;
var amount = item?.Properties?.GetInt(HealProperty) ?? 0;
if (health == null || amount <= 0 || health.Value <= 0 || health.Value >= health.BaseValue)
return false;
health.Value = Math.Min(health.BaseValue, health.Value + amount);
return true;
}
}
}
+23
View File
@@ -0,0 +1,23 @@
using Cthangover.Core.Items;
using Cthangover.Core.Settings;
namespace Cthangover.Mods.Items
{
/// <summary>
/// Item action that needs to know whom it is applied to. The core
/// <see cref="IItemAction"/> contract has no target, so an effect on a party
/// member (healing) could not tell which member was picked in
/// <see cref="Cthangover.Core.UI.Inventory.CharacterTargetPanel"/>. Items with
/// <see cref="ItemType.TargetUsed"/> whose action implements this interface get
/// <see cref="UseOn"/> with the chosen character; other actions keep receiving
/// the plain <see cref="IItemAction.UseAction"/>.
/// </summary>
public interface ITargetedItemAction : IItemAction
{
/// <summary>
/// Applies the item to <paramref name="target"/>. Returns <c>false</c> when
/// nothing happened (e.g. healing at full health), so the item is not spent.
/// </summary>
bool UseOn(IItem item, CharacterInfoData target);
}
}
+42
View File
@@ -0,0 +1,42 @@
using Cthangover.Core.Actions;
namespace Cthangover.Mods.Items
{
/// <summary>
/// <c>action item.add id=resource/branches [count=3]</c> — puts items into the
/// player's inventory from a scenario: a chest, a gift, a pickup on an RPG map.
/// <c>count</c> defaults to 1; an unknown item id is reported by the inventory
/// and adds nothing.
/// </summary>
public class ItemAddAction : IScenarioAction
{
/// <summary>DSL name.</summary>
public string Name => "item.add";
/// <summary>Reads <c>id</c> and <c>count</c> and adds the stack.</summary>
public void Run(IActionContext ctx)
{
var id = ctx.GetParam("id");
if (string.IsNullOrWhiteSpace(id))
{
ctx.LogWarning("INVENTORY", "item.add: missing 'id'");
return;
}
var countText = ctx.GetParam("count");
var count = 1;
if (!string.IsNullOrWhiteSpace(countText) && (!int.TryParse(countText, out count) || count <= 0))
{
ctx.LogWarning("INVENTORY", $"item.add: 'count' must be a positive number, got '{countText}'");
return;
}
if (ctx.Inventory == null)
{
ctx.LogWarning("INVENTORY", "item.add: no inventory in this game");
return;
}
ctx.Inventory.Add(id.Trim(), count);
}
}
}
+17
View File
@@ -88,6 +88,23 @@ namespace Cthangover.Core.UI.Inventory
_count.Modulate = dimmed ? IconDimmed : Colors.White;
}
/// <summary>
/// Overrides what <see cref="UpdateInfo"/> derived from the item's action,
/// for grids where the action is not the point — the trade window shows a
/// price in the corner tag (<paramref name="tag"/>, <c>null</c> hides it) and
/// dims what cannot be traded. Call after <see cref="Construct"/>.
/// </summary>
public void Decorate(string tag, bool dimmed)
{
if (!_built)
return;
_tag.Visible = tag != null;
if (tag != null)
_tagLabel.Text = tag;
_icon.Modulate = dimmed ? IconDimmed : Colors.White;
_count.Modulate = dimmed ? IconDimmed : Colors.White;
}
/// <summary>Switches the frame to the gold selected state; the panel owns this flag.</summary>
public void SetSelected(bool selected)
{
+5 -1
View File
@@ -10,6 +10,7 @@ using Cthangover.Core.UI.Theming;
using Godot;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Mods.Items;
using Cthangover.Mods.Interface.Theming;
namespace Cthangover.Core.UI.Inventory
{
@@ -978,7 +979,10 @@ namespace Cthangover.Core.UI.Inventory
if (!HasActionPoint())
return;
if (!item.ItemAction.UseAction(item))
var applied = item.ItemAction is ITargetedItemAction targeted
? targeted.UseOn(item, info)
: item.ItemAction.UseAction(item);
if (!applied)
{
SetMessage(MessageKind.Error, InventoryItemUi.Translate("ui/inventory/applied_failed"));
_targetOpen = false;