]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Action Upgrade System (#22277)
authorkeronshb <54602815+keronshb@users.noreply.github.com>
Fri, 15 Dec 2023 09:41:44 +0000 (04:41 -0500)
committerGitHub <noreply@github.com>
Fri, 15 Dec 2023 09:41:44 +0000 (02:41 -0700)
* Adds uses before delay so actions can be used multiple times before cooldown

* adds methods to get remaining charges, to set uses before delay, and to set use delay

* adds method to change action name

* moves set usedelay

* action upgrade ECS

* adds method to reset remaining uses

* adds upgrade events

* refactors action upgrade event and adds logic to parse it

* fix serialization issue

* adds level up draft method

* adds action commands and a command to upgrade an action

* more warning lines to help

* Gets action to upgrade properly

* Removes unneeded fields from the action upgrade component and now properly raises the level of the new action

* Cleans up dead code and comments

* Fixes punctuation in actions-commands and adds a TryUpgradeAction method.

* removes TODO comment

* robust fix

* removes RT

* readds RT

* update RT to 190

* removes change name method

* removes remaining uses & related fields and adds that functionality to charges

* Adds Charges to action tooltips that require it

14 files changed:
Content.Client/Actions/ActionsSystem.cs
Content.Client/Actions/UI/ActionAlertTooltip.cs
Content.Client/Stylesheets/StyleNano.cs
Content.Client/UserInterface/Systems/Actions/ActionUIController.cs
Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs
Content.Client/UserInterface/Systems/Actions/Controls/ActionTooltip.xaml
Content.Server/Commands/ActionCommands.cs [new file with mode: 0644]
Content.Shared/Actions/ActionUpgradeComponent.cs [new file with mode: 0644]
Content.Shared/Actions/ActionUpgradeSystem.cs [new file with mode: 0644]
Content.Shared/Actions/BaseActionComponent.cs
Content.Shared/Actions/Events/ActionUpgradeEvent.cs [new file with mode: 0644]
Content.Shared/Actions/SharedActionsSystem.cs
Resources/Locale/en-US/actions/actions/actions-commands.ftl [new file with mode: 0644]
Resources/Prototypes/Magic/projectile_spells.yml

index b67c1bd5b97454a75ca1b1192cdf08ad0dfcf084..508f3404bac6262090b1ee7c4804d29c80acf33b 100644 (file)
@@ -87,6 +87,8 @@ namespace Content.Client.Actions
             component.Cooldown = state.Cooldown;
             component.UseDelay = state.UseDelay;
             component.Charges = state.Charges;
+            component.MaxCharges = state.MaxCharges;
+            component.RenewCharges = state.RenewCharges;
             component.Container = EnsureEntity<T>(state.Container, uid);
             component.EntityIcon = EnsureEntity<T>(state.EntityIcon, uid);
             component.CheckCanInteract = state.CheckCanInteract;
index f48350d77220af8cdf0238a5df15cc5234460cba..ddc498b6e91296faeb8243b0c93a6524d0a1bfd7 100644 (file)
@@ -21,7 +21,7 @@ namespace Content.Client.Actions.UI
         /// </summary>
         public (TimeSpan Start, TimeSpan End)? Cooldown { get; set; }
 
-        public ActionAlertTooltip(FormattedMessage name, FormattedMessage? desc, string? requires = null)
+        public ActionAlertTooltip(FormattedMessage name, FormattedMessage? desc, string? requires = null, FormattedMessage? charges = null)
         {
             _gameTiming = IoCManager.Resolve<IGameTiming>();
 
@@ -52,6 +52,17 @@ namespace Content.Client.Actions.UI
                 vbox.AddChild(description);
             }
 
+            if (charges != null && !string.IsNullOrWhiteSpace(charges.ToString()))
+            {
+                var chargesLabel = new RichTextLabel
+                {
+                    MaxWidth = TooltipTextMaxWidth,
+                    StyleClasses = { StyleNano.StyleClassTooltipActionCharges }
+                };
+                chargesLabel.SetMessage(charges);
+                vbox.AddChild(chargesLabel);
+            }
+
             vbox.AddChild(_cooldownLabel = new RichTextLabel
             {
                 MaxWidth = TooltipTextMaxWidth,
index 2ef5a63ac7311bc1262ab84f5978ac96ca4907bf..9f656297c82845ad8f6a188544e8343c16343a29 100644 (file)
@@ -56,6 +56,7 @@ namespace Content.Client.Stylesheets
         public const string StyleClassTooltipActionDescription = "tooltipActionDesc";
         public const string StyleClassTooltipActionCooldown = "tooltipActionCooldown";
         public const string StyleClassTooltipActionRequirements = "tooltipActionCooldown";
+        public const string StyleClassTooltipActionCharges = "tooltipActionCharges";
         public const string StyleClassHotbarSlotNumber = "hotbarSlotNumber";
         public const string StyleClassActionSearchBox = "actionSearchBox";
         public const string StyleClassActionMenuItemRevoked = "actionMenuItemRevoked";
@@ -940,6 +941,10 @@ namespace Content.Client.Stylesheets
                 {
                     new StyleProperty("font", notoSans15)
                 }),
+                new StyleRule(new SelectorElement(typeof(RichTextLabel), new[] {StyleClassTooltipActionCharges}, null, null), new[]
+                {
+                    new StyleProperty("font", notoSans15)
+                }),
 
                 // small number for the entity counter in the entity menu
                 new StyleRule(new SelectorElement(typeof(Label), new[] {ContextMenuElement.StyleClassEntityMenuIconLabel}, null, null), new[]
index 635647c89091d71ec2c5f35a884efce3ec6c4bf7..aae06965ef92cf2581b161b8430420eb92347cc4 100644 (file)
@@ -171,7 +171,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
 
         // Is the action currently valid?
         if (!action.Enabled
-            || action.Charges is 0
+            || action is { Charges: 0, RenewCharges: false }
             || action.Cooldown.HasValue && action.Cooldown.Value.End > _timing.CurTime)
         {
             // The user is targeting with this action, but it is not valid. Maybe mark this click as
index aab160a162d0ac820ae245c345c85b6e2afdd0f9..3cd172b080219ebda082bd9ed46fdef771d7f3e4 100644 (file)
@@ -191,6 +191,12 @@ public sealed class ActionButton : Control, IEntityControl
         var name = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityName));
         var decr = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityDescription));
 
+        if (_action is { Charges: not null })
+        {
+            var charges = FormattedMessage.FromMarkupPermissive(Loc.GetString($"Charges: {_action.Charges.Value.ToString()}/{_action.MaxCharges.ToString()}"));
+            return new ActionAlertTooltip(name, decr, charges: charges);
+        }
+
         return new ActionAlertTooltip(name, decr);
     }
 
index 338a297453913cacabf4116d34c297fb703697e4..22893ccef4c2ab2dbc237c6a60ac037b08c345d7 100644 (file)
@@ -5,5 +5,6 @@
     <BoxContainer Orientation="Vertical" RectClipContent="True">
         <RichTextLabel MaxWidth="350" StyleClasses="StyleClassTooltipActionTitle"/>
         <RichTextLabel MaxWidth="350" StyleClasses="StyleClassTooltipActionDescription"/>
+        <RichTextLabel MaxWidth="350" StyleClasses="StyleClassTooltipActionCharges"/>
     </BoxContainer>
 </controls:ActionTooltip>
diff --git a/Content.Server/Commands/ActionCommands.cs b/Content.Server/Commands/ActionCommands.cs
new file mode 100644 (file)
index 0000000..280bf75
--- /dev/null
@@ -0,0 +1,81 @@
+using Content.Server.Administration;
+using Content.Shared.Actions;
+using Content.Shared.Administration;
+using Robust.Shared.Console;
+
+namespace Content.Server.Commands;
+
+[AdminCommand(AdminFlags.Fun)]
+internal sealed class UpgradeActionCommand : IConsoleCommand
+{
+    [Dependency] private readonly IEntityManager _entMan = default!;
+
+    public string Command => "upgradeaction";
+    public string Description => Loc.GetString("upgradeaction-command-description");
+    public string Help => Loc.GetString("upgradeaction-command-help");
+
+    public void Execute(IConsoleShell shell, string argStr, string[] args)
+    {
+        if (args.Length < 1)
+        {
+            shell.WriteLine(Loc.GetString("upgradeaction-command-need-one-argument"));
+            return;
+        }
+
+        if (args.Length > 2)
+        {
+            shell.WriteLine(Loc.GetString("upgradeaction-command-max-two-arguments"));
+            return;
+        }
+
+        var actionUpgrade = _entMan.EntitySysManager.GetEntitySystem<ActionUpgradeSystem>();
+        var id = args[0];
+
+        if (!EntityUid.TryParse(id, out var uid))
+        {
+            shell.WriteLine(Loc.GetString("upgradeaction-command-incorrect-entityuid-format"));
+            return;
+        }
+
+        if (!_entMan.EntityExists(uid))
+        {
+            shell.WriteLine(Loc.GetString("upgradeaction-command-entity-does-not-exist"));
+            return;
+        }
+
+        if (!_entMan.TryGetComponent<ActionUpgradeComponent>(uid, out var actionUpgradeComponent))
+        {
+            shell.WriteLine(Loc.GetString("upgradeaction-command-entity-is-not-action"));
+            return;
+        }
+
+        if (args.Length == 1)
+        {
+            if (!actionUpgrade.TryUpgradeAction(uid, actionUpgradeComponent))
+            {
+                shell.WriteLine(Loc.GetString("upgradeaction-command-cannot-level-up"));
+                return;
+            }
+        }
+
+        if (args.Length == 2)
+        {
+            var levelArg = args[1];
+
+            if (!int.TryParse(levelArg, out var level))
+            {
+                shell.WriteLine(Loc.GetString("upgradeaction-command-second-argument-not-number"));
+                return;
+            }
+
+            if (level <= 0)
+            {
+                shell.WriteLine(Loc.GetString("upgradeaction-command-less-than-required-level"));
+                return;
+            }
+
+            if (!actionUpgrade.TryUpgradeAction(uid, actionUpgradeComponent, level))
+                shell.WriteLine(Loc.GetString("upgradeaction-command-cannot-level-up"));
+        }
+    }
+}
diff --git a/Content.Shared/Actions/ActionUpgradeComponent.cs b/Content.Shared/Actions/ActionUpgradeComponent.cs
new file mode 100644 (file)
index 0000000..0d6a813
--- /dev/null
@@ -0,0 +1,24 @@
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Actions;
+
+// For actions that can use basic upgrades
+// Not all actions should be upgradable
+[RegisterComponent, NetworkedComponent, Access(typeof(ActionUpgradeSystem))]
+public sealed partial class ActionUpgradeComponent : Component
+{
+    /// <summary>
+    ///     Current Level of the action.
+    /// </summary>
+    public int Level = 1;
+
+    /// <summary>
+    ///     What level(s) effect this action?
+    ///     You can skip levels, so you can have this entity change at level 2 but then won't change again until level 5.
+    /// </summary>
+    [DataField("effectedLevels"), ViewVariables]
+    public Dictionary<int, EntProtoId> EffectedLevels = new();
+
+    // TODO: Branching level upgrades
+}
diff --git a/Content.Shared/Actions/ActionUpgradeSystem.cs b/Content.Shared/Actions/ActionUpgradeSystem.cs
new file mode 100644 (file)
index 0000000..f489779
--- /dev/null
@@ -0,0 +1,149 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Content.Shared.Actions.Events;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Actions;
+
+public sealed class ActionUpgradeSystem : EntitySystem
+{
+    [Dependency] private readonly SharedActionsSystem _actions = default!;
+    [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
+    [Dependency] private readonly EntityManager _entityManager = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<ActionUpgradeComponent, ActionUpgradeEvent>(OnActionUpgradeEvent);
+    }
+
+    private void OnActionUpgradeEvent(EntityUid uid, ActionUpgradeComponent component, ActionUpgradeEvent args)
+    {
+        if (!CanLevelUp(args.NewLevel, component.EffectedLevels, out var newActionProto)
+            || !_actions.TryGetActionData(uid, out var actionComp))
+            return;
+
+        var originalContainer = actionComp.Container;
+        var originalAttachedEntity = actionComp.AttachedEntity;
+
+        _actionContainer.RemoveAction(uid, actionComp);
+
+        EntityUid? upgradedActionId = null;
+        if (originalContainer != null
+            && TryComp<ActionsContainerComponent>(originalContainer.Value, out var actionContainerComp))
+        {
+            upgradedActionId = _actionContainer.AddAction(originalContainer.Value, newActionProto, actionContainerComp);
+
+            if (originalAttachedEntity != null)
+                _actions.GrantContainedActions(originalAttachedEntity.Value, originalContainer.Value);
+            else
+                _actions.GrantContainedActions(originalContainer.Value, originalContainer.Value);
+        }
+        else if (originalAttachedEntity != null)
+        {
+            upgradedActionId = _actionContainer.AddAction(originalAttachedEntity.Value, newActionProto);
+        }
+
+        if (!TryComp<ActionUpgradeComponent>(upgradedActionId, out var upgradeComp))
+            return;
+
+        upgradeComp.Level = args.NewLevel;
+
+        // TODO: Preserve ordering of actions
+
+        _entityManager.DeleteEntity(uid);
+    }
+
+    public bool TryUpgradeAction(EntityUid? actionId, ActionUpgradeComponent? actionUpgradeComponent = null, int newLevel = 0)
+    {
+        if (!TryGetActionUpgrade(actionId, out var actionUpgradeComp))
+            return false;
+
+        actionUpgradeComponent ??= actionUpgradeComp;
+        DebugTools.AssertNotNull(actionUpgradeComponent);
+        DebugTools.AssertNotNull(actionId);
+
+        if (newLevel < 1)
+            newLevel = actionUpgradeComponent.Level + 1;
+
+        if (!CanLevelUp(newLevel, actionUpgradeComponent.EffectedLevels, out _))
+            return false;
+
+        UpgradeAction(actionId, actionUpgradeComp);
+        return true;
+    }
+
+    // TODO: Add checks for branching upgrades
+    private bool CanLevelUp(
+        int newLevel,
+        Dictionary<int, EntProtoId> levelDict,
+        [NotNullWhen(true)]out EntProtoId? newLevelProto)
+    {
+        newLevelProto = null;
+
+        if (levelDict.Count < 1)
+            return false;
+
+        var canLevel = false;
+        var finalLevel = levelDict.Keys.ToList()[levelDict.Keys.Count - 1];
+
+        foreach (var (level, proto) in levelDict)
+        {
+            if (newLevel != level || newLevel > finalLevel)
+                continue;
+
+            canLevel = true;
+            newLevelProto = proto;
+            DebugTools.AssertNotNull(newLevelProto);
+            break;
+        }
+
+        return canLevel;
+    }
+
+    /// <summary>
+    ///     Raises a level by one
+    /// </summary>
+    public void UpgradeAction(EntityUid? actionId, ActionUpgradeComponent? actionUpgradeComponent = null, int newLevel = 0)
+    {
+        if (!TryGetActionUpgrade(actionId, out var actionUpgradeComp))
+            return;
+
+        actionUpgradeComponent ??= actionUpgradeComp;
+        DebugTools.AssertNotNull(actionUpgradeComponent);
+        DebugTools.AssertNotNull(actionId);
+
+        if (newLevel < 1)
+            newLevel = actionUpgradeComponent.Level + 1;
+
+        RaiseActionUpgradeEvent(newLevel, actionId.Value);
+    }
+
+    private void RaiseActionUpgradeEvent(int level, EntityUid actionId)
+    {
+        var ev = new ActionUpgradeEvent(level, actionId);
+        RaiseLocalEvent(actionId, ev);
+    }
+
+    public bool TryGetActionUpgrade(
+        [NotNullWhen(true)] EntityUid? uid,
+        [NotNullWhen(true)] out ActionUpgradeComponent? result,
+        bool logError = true)
+    {
+        result = null;
+        if (!Exists(uid))
+            return false;
+
+        if (!TryComp<ActionUpgradeComponent>(uid, out var actionUpgradeComponent))
+        {
+            Log.Error($"Failed to get action upgrade from action entity: {ToPrettyString(uid.Value)}");
+            return false;
+        }
+
+        result = actionUpgradeComponent;
+        DebugTools.AssertOwner(uid, result);
+        return true;
+    }
+}
index 0eccdddc4c246e10dc2228ff76d3c9beb36112fd..291d9a3ea29f3d7a1c5d452a7a83f3d246a1a251 100644 (file)
@@ -66,9 +66,21 @@ public abstract partial class BaseActionComponent : Component
     /// <summary>
     ///     Convenience tool for actions with limited number of charges. Automatically decremented on use, and the
     ///     action is disabled when it reaches zero. Does NOT automatically remove the action from the action bar.
+    ///     However, charges will regenerate if <see cref="RenewCharges"/> is enabled and the action will not disable
+    ///     when charges reach zero.
     /// </summary>
     [DataField("charges")] public int? Charges;
 
+    /// <summary>
+    ///     The max charges this action has, set automatically from <see cref="Charges"/>
+    /// </summary>
+    public int MaxCharges;
+
+    /// <summary>
+    ///     If enabled, charges will regenerate after a <see cref="Cooldown"/> is complete
+    /// </summary>
+    [DataField("renewCharges")]public bool RenewCharges;
+
     /// <summary>
     /// The entity that contains this action. If the action is innate, this may be the user themselves.
     /// This should almost always be non-null.
@@ -159,6 +171,8 @@ public abstract class BaseActionComponentState : ComponentState
     public (TimeSpan Start, TimeSpan End)? Cooldown;
     public TimeSpan? UseDelay;
     public int? Charges;
+    public int MaxCharges;
+    public bool RenewCharges;
     public NetEntity? Container;
     public NetEntity? EntityIcon;
     public bool CheckCanInteract;
@@ -186,6 +200,8 @@ public abstract class BaseActionComponentState : ComponentState
         Cooldown = component.Cooldown;
         UseDelay = component.UseDelay;
         Charges = component.Charges;
+        MaxCharges = component.MaxCharges;
+        RenewCharges = component.RenewCharges;
         CheckCanInteract = component.CheckCanInteract;
         ClientExclusive = component.ClientExclusive;
         Priority = component.Priority;
diff --git a/Content.Shared/Actions/Events/ActionUpgradeEvent.cs b/Content.Shared/Actions/Events/ActionUpgradeEvent.cs
new file mode 100644 (file)
index 0000000..40ff716
--- /dev/null
@@ -0,0 +1,13 @@
+namespace Content.Shared.Actions.Events;
+
+public sealed class ActionUpgradeEvent : EntityEventArgs
+{
+    public int NewLevel;
+    public EntityUid? ActionId;
+
+    public ActionUpgradeEvent(int newLevel, EntityUid? actionId)
+    {
+        NewLevel = newLevel;
+        ActionId = actionId;
+    }
+}
index 675727167e8e7dd5499ee42807fa222c9182ca59..4323a461148864e8c05603f7aaaecde0702aeb05 100644 (file)
@@ -30,11 +30,16 @@ public abstract class SharedActionsSystem : EntitySystem
     [Dependency] private readonly SharedAudioSystem _audio = default!;
     [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
     [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
+    [Dependency] private readonly MetaDataSystem _metaData = default!;
 
     public override void Initialize()
     {
         base.Initialize();
 
+        SubscribeLocalEvent<InstantActionComponent, MapInitEvent>(OnInit);
+        SubscribeLocalEvent<EntityTargetActionComponent, MapInitEvent>(OnInit);
+        SubscribeLocalEvent<WorldTargetActionComponent, MapInitEvent>(OnInit);
+
         SubscribeLocalEvent<ActionsComponent, DidEquipEvent>(OnDidEquip);
         SubscribeLocalEvent<ActionsComponent, DidEquipHandEvent>(OnHandEquipped);
         SubscribeLocalEvent<ActionsComponent, DidUnequipEvent>(OnDidUnequip);
@@ -56,6 +61,12 @@ public abstract class SharedActionsSystem : EntitySystem
         SubscribeAllEvent<RequestPerformActionEvent>(OnActionRequest);
     }
 
+    private void OnInit(EntityUid uid, BaseActionComponent component, MapInitEvent args)
+    {
+        if (component.Charges != null)
+            component.MaxCharges = component.Charges.Value;
+    }
+
     private void OnShutdown(EntityUid uid, ActionsComponent component, ComponentShutdown args)
     {
         foreach (var act in component.Actions)
@@ -165,6 +176,31 @@ public abstract class SharedActionsSystem : EntitySystem
         Dirty(actionId.Value, action);
     }
 
+    public void SetUseDelay(EntityUid? actionId, TimeSpan? delay)
+    {
+        if (!TryGetActionData(actionId, out var action) || action.UseDelay == delay)
+            return;
+
+        action.UseDelay = delay;
+        UpdateAction(actionId, action);
+        Dirty(actionId.Value, action);
+    }
+
+    public void ReduceUseDelay(EntityUid? actionId, TimeSpan? lowerDelay)
+    {
+        if (!TryGetActionData(actionId, out var action))
+            return;
+
+        if (action.UseDelay != null && lowerDelay != null)
+            action.UseDelay = action.UseDelay - lowerDelay;
+
+        if (action.UseDelay < TimeSpan.Zero)
+            action.UseDelay = null;
+
+        UpdateAction(actionId, action);
+        Dirty(actionId.Value, action);
+    }
+
     private void OnRejuventate(EntityUid uid, ActionsComponent component, RejuvenateEvent args)
     {
         foreach (var act in component.Actions)
@@ -218,6 +254,51 @@ public abstract class SharedActionsSystem : EntitySystem
         Dirty(actionId.Value, action);
     }
 
+    public int? GetCharges(EntityUid? actionId)
+    {
+        if (!TryGetActionData(actionId, out var action))
+            return null;
+
+        return action.Charges;
+    }
+
+    public void AddCharges(EntityUid? actionId, int addCharges)
+    {
+        if (!TryGetActionData(actionId, out var action) || action.Charges == null || addCharges < 1)
+            return;
+
+        action.Charges += addCharges;
+        UpdateAction(actionId, action);
+        Dirty(actionId.Value, action);
+    }
+
+    public void RemoveCharges(EntityUid? actionId, int? removeCharges)
+    {
+        if (!TryGetActionData(actionId, out var action) || action.Charges == null)
+            return;
+
+        if (removeCharges == null)
+            action.Charges = removeCharges;
+        else
+            action.Charges -= removeCharges;
+
+        if (action.Charges is < 0)
+            action.Charges = null;
+
+        UpdateAction(actionId, action);
+        Dirty(actionId.Value, action);
+    }
+
+    public void ResetCharges(EntityUid? actionId)
+    {
+        if (!TryGetActionData(actionId, out var action))
+            return;
+
+        action.Charges = action.MaxCharges;
+        UpdateAction(actionId, action);
+        Dirty(actionId.Value, action);
+    }
+
     private void OnActionsGetState(EntityUid uid, ActionsComponent component, ref ComponentGetState args)
     {
         args.State = new ActionsComponentState(GetNetEntitySet(component.Actions));
@@ -261,9 +342,14 @@ public abstract class SharedActionsSystem : EntitySystem
             return;
 
         var curTime = GameTiming.CurTime;
+        // TODO: Check for charge recovery timer
         if (action.Cooldown.HasValue && action.Cooldown.Value.End > curTime)
             return;
 
+        // TODO: Replace with individual charge recovery when we have the visuals to aid it
+        if (action is { Charges: < 1, RenewCharges: true })
+            ResetCharges(actionEnt);
+
         BaseActionEvent? performEvent = null;
 
         // Validate request by checking action blockers and the like:
@@ -438,12 +524,12 @@ public abstract class SharedActionsSystem : EntitySystem
         {
             dirty = true;
             action.Charges--;
-            if (action.Charges == 0)
+            if (action is { Charges: 0, RenewCharges: false })
                 action.Enabled = false;
         }
 
         action.Cooldown = null;
-        if (action.UseDelay != null)
+        if (action is { UseDelay: not null, Charges: null or < 1 })
         {
             dirty = true;
             action.Cooldown = (curTime, curTime + action.UseDelay.Value);
diff --git a/Resources/Locale/en-US/actions/actions/actions-commands.ftl b/Resources/Locale/en-US/actions/actions/actions-commands.ftl
new file mode 100644 (file)
index 0000000..c0cd59c
--- /dev/null
@@ -0,0 +1,12 @@
+## Actions Commands loc
+
+## Upgradeaction command loc
+upgradeaction-command-need-one-argument = upgradeaction needs at least one argument, the action entity uid. The second optional argument is a specified level.
+upgradeaction-command-max-two-arguments = upgradeaction has a maximum of two arguments, the action entity uid and the (optional) level to set.
+upgradeaction-command-second-argument-not-number = upgradeaction's second argument can only be a number.
+upgradeaction-command-less-than-required-level = upgradeaction cannot accept a level of 0 or lower.
+upgradeaction-command-incorrect-entityuid-format = You must use a valid entityuid format for upgradeaction.
+upgradeaction-command-entity-does-not-exist = This entity does not exist, a valid entity is required for upgradeaction.
+upgradeaction-command-entity-is-not-action = This entity doesn't have the action upgrade component, so this action cannot be leveled.
+upgradeaction-command-cannot-level-up = The action cannot be leveled up.
+upgradeaction-command-description = Upgrades an action by one level, or to the specified level, if applicable.
index 8d7b2ffff0586c3fe82f18254f94e2a57933ae9a..196472fe7b2f9d20c59802b74326c3a0e1303546 100644 (file)
@@ -5,7 +5,34 @@
   noSpawn: true
   components:
   - type: WorldTargetAction
-    useDelay: 30
+    useDelay: 15
+    itemIconStyle: BigAction
+    checkCanAccess: false
+    range: 60
+    sound: !type:SoundPathSpecifier
+      path: /Audio/Magic/fireball.ogg
+    icon:
+      sprite: Objects/Magic/magicactions.rsi
+      state: fireball
+    event: !type:ProjectileSpellEvent
+      prototype: ProjectileFireball
+      posData: !type:TargetCasterPos
+      speech: action-speech-spell-fireball
+  - type: ActionUpgrade
+    effectedLevels:
+      2: ActionFireballII
+
+- type: entity
+  id: ActionFireballII
+  parent: ActionFireball
+  name: Fireball II
+  description: Fire three explosive fireball towards the clicked location.
+  noSpawn: true
+  components:
+  - type: WorldTargetAction
+    useDelay: 5
+    charges: 3
+    renewCharges: true
     itemIconStyle: BigAction
     checkCanAccess: false
     range: 60