diff --git a/Content.Server/Commands/ActionCommands.cs b/Content.Server/Commands/ActionCommands.cs index 5ce29b6d8e..fb50cfb3a3 100644 --- a/Content.Server/Commands/ActionCommands.cs +++ b/Content.Server/Commands/ActionCommands.cs @@ -51,7 +51,7 @@ internal sealed class UpgradeActionCommand : IConsoleCommand if (args.Length == 1) { - if (!actionUpgrade.TryUpgradeAction(uid, actionUpgradeComponent)) + if (!actionUpgrade.TryUpgradeAction(uid, out _, actionUpgradeComponent)) { shell.WriteLine(Loc.GetString("upgradeaction-command-cannot-level-up")); return; @@ -74,7 +74,7 @@ internal sealed class UpgradeActionCommand : IConsoleCommand return; } - if (!actionUpgrade.TryUpgradeAction(uid, actionUpgradeComponent, level)) + if (!actionUpgrade.TryUpgradeAction(uid, out _, actionUpgradeComponent, level)) shell.WriteLine(Loc.GetString("upgradeaction-command-cannot-level-up")); } } diff --git a/Content.Shared/Actions/ActionContainerSystem.cs b/Content.Shared/Actions/ActionContainerSystem.cs index 0fe2b83b11..17bcf11bff 100644 --- a/Content.Shared/Actions/ActionContainerSystem.cs +++ b/Content.Shared/Actions/ActionContainerSystem.cs @@ -30,6 +30,7 @@ public sealed class ActionContainerSystem : EntitySystem SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnEntityRemoved); SubscribeLocalEvent(OnEntityInserted); + SubscribeLocalEvent(OnActionAdded); SubscribeLocalEvent(OnMindAdded); SubscribeLocalEvent(OnMindRemoved); } @@ -38,7 +39,6 @@ public sealed class ActionContainerSystem : EntitySystem { if(!_mind.TryGetMind(uid, out var mindId, out _)) return; - if (!TryComp(mindId, out var mindActionContainerComp)) return; @@ -174,6 +174,61 @@ public sealed class ActionContainerSystem : EntitySystem DebugTools.AssertEqual(oldContainer.Container.Count, 0); } + /// + /// Transfers an actions from one container to another, while changing the attached entity. + /// + /// + /// This will actually remove and then re-grant the action. + /// Useful where you need to transfer from one container to another but also change the attached entity (ie spellbook > mind > user) + /// + public void TransferActionWithNewAttached( + EntityUid actionId, + EntityUid newContainer, + EntityUid newAttached, + BaseActionComponent? action = null, + ActionsContainerComponent? container = null) + { + if (!_actions.ResolveActionData(actionId, ref action)) + return; + + if (action.Container == newContainer) + return; + + var attached = newAttached; + if (!AddAction(newContainer, actionId, action, container)) + return; + + DebugTools.AssertEqual(action.Container, newContainer); + _actions.AddActionDirect(newAttached, actionId, action: action); + + DebugTools.AssertEqual(action.AttachedEntity, attached); + } + + /// + /// Transfers all actions from one container to another, while changing the attached entity. + /// + /// + /// This will actually remove and then re-grant the action. + /// Useful where you need to transfer from one container to another but also change the attached entity (ie spellbook > mind > user) + /// + public void TransferAllActionsWithNewAttached( + EntityUid from, + EntityUid to, + EntityUid newAttached, + ActionsContainerComponent? oldContainer = null, + ActionsContainerComponent? newContainer = null) + { + if (!Resolve(from, ref oldContainer) || !Resolve(to, ref newContainer)) + return; + + foreach (var action in oldContainer.Container.ContainedEntities.ToArray()) + { + TransferActionWithNewAttached(action, to, newAttached, container: newContainer); + } + + DebugTools.AssertEqual(oldContainer.Container.Count, 0); + } + /// /// Adds a pre-existing action to an action container. If the action is already in some container it will first remove it. /// @@ -281,6 +336,12 @@ public sealed class ActionContainerSystem : EntitySystem RaiseLocalEvent(uid, ref ev); data.Container = null; } + + private void OnActionAdded(EntityUid uid, ActionsContainerComponent component, ActionAddedEvent args) + { + if (TryComp(uid, out var mindComp) && mindComp.OwnedEntity != null && HasComp(mindComp.OwnedEntity.Value)) + _actions.GrantContainedAction(mindComp.OwnedEntity.Value, uid, args.Action); + } } /// diff --git a/Content.Shared/Actions/ActionUpgradeComponent.cs b/Content.Shared/Actions/ActionUpgradeComponent.cs index 0d6a813526..d12ce339c8 100644 --- a/Content.Shared/Actions/ActionUpgradeComponent.cs +++ b/Content.Shared/Actions/ActionUpgradeComponent.cs @@ -11,6 +11,7 @@ public sealed partial class ActionUpgradeComponent : Component /// /// Current Level of the action. /// + [ViewVariables] public int Level = 1; /// diff --git a/Content.Shared/Actions/ActionUpgradeSystem.cs b/Content.Shared/Actions/ActionUpgradeSystem.cs index f489779493..b89b462814 100644 --- a/Content.Shared/Actions/ActionUpgradeSystem.cs +++ b/Content.Shared/Actions/ActionUpgradeSystem.cs @@ -21,7 +21,7 @@ public sealed class ActionUpgradeSystem : EntitySystem private void OnActionUpgradeEvent(EntityUid uid, ActionUpgradeComponent component, ActionUpgradeEvent args) { - if (!CanLevelUp(args.NewLevel, component.EffectedLevels, out var newActionProto) + if (!CanUpgrade(args.NewLevel, component.EffectedLevels, out var newActionProto) || !_actions.TryGetActionData(uid, out var actionComp)) return; @@ -56,8 +56,9 @@ public sealed class ActionUpgradeSystem : EntitySystem _entityManager.DeleteEntity(uid); } - public bool TryUpgradeAction(EntityUid? actionId, ActionUpgradeComponent? actionUpgradeComponent = null, int newLevel = 0) + public bool TryUpgradeAction(EntityUid? actionId, out EntityUid? upgradeActionId, ActionUpgradeComponent? actionUpgradeComponent = null, int newLevel = 0) { + upgradeActionId = null; if (!TryGetActionUpgrade(actionId, out var actionUpgradeComp)) return false; @@ -68,21 +69,26 @@ public sealed class ActionUpgradeSystem : EntitySystem if (newLevel < 1) newLevel = actionUpgradeComponent.Level + 1; - if (!CanLevelUp(newLevel, actionUpgradeComponent.EffectedLevels, out _)) + if (!CanLevelUp(newLevel, actionUpgradeComponent.EffectedLevels)) return false; - UpgradeAction(actionId, actionUpgradeComp); + actionUpgradeComponent.Level = newLevel; + + // If it can level up but can't upgrade, still return true and return current actionId as the upgradeId. + if (!CanUpgrade(newLevel, actionUpgradeComponent.EffectedLevels, out var newActionProto)) + { + upgradeActionId = actionId; + DebugTools.AssertNotNull(upgradeActionId); + return true; + } + + upgradeActionId = UpgradeAction(actionId, actionUpgradeComp, newActionProto, newLevel); + DebugTools.AssertNotNull(upgradeActionId); return true; } - // TODO: Add checks for branching upgrades - private bool CanLevelUp( - int newLevel, - Dictionary levelDict, - [NotNullWhen(true)]out EntProtoId? newLevelProto) + private bool CanLevelUp(int newLevel, Dictionary levelDict) { - newLevelProto = null; - if (levelDict.Count < 1) return false; @@ -91,25 +97,47 @@ public sealed class ActionUpgradeSystem : EntitySystem foreach (var (level, proto) in levelDict) { - if (newLevel != level || newLevel > finalLevel) + if (newLevel > finalLevel) continue; - canLevel = true; - newLevelProto = proto; - DebugTools.AssertNotNull(newLevelProto); - break; + if ((newLevel <= finalLevel && newLevel != level) || newLevel == level) + { + canLevel = true; + break; + } } return canLevel; } + private bool CanUpgrade(int newLevel, Dictionary levelDict, [NotNullWhen(true)]out EntProtoId? newLevelProto) + { + var canUpgrade = false; + newLevelProto = null; + + var finalLevel = levelDict.Keys.ToList()[levelDict.Keys.Count - 1]; + + foreach (var (level, proto) in levelDict) + { + if (newLevel != level || newLevel > finalLevel) + continue; + + canUpgrade = true; + newLevelProto = proto; + DebugTools.AssertNotNull(newLevelProto); + break; + } + + return canUpgrade; + } + /// /// Raises a level by one /// - public void UpgradeAction(EntityUid? actionId, ActionUpgradeComponent? actionUpgradeComponent = null, int newLevel = 0) + public EntityUid? UpgradeAction(EntityUid? actionId, ActionUpgradeComponent? actionUpgradeComponent = null, EntProtoId? newActionProto = null, int newLevel = 0) { if (!TryGetActionUpgrade(actionId, out var actionUpgradeComp)) - return; + return null; actionUpgradeComponent ??= actionUpgradeComp; DebugTools.AssertNotNull(actionUpgradeComponent); @@ -118,7 +146,47 @@ public sealed class ActionUpgradeSystem : EntitySystem if (newLevel < 1) newLevel = actionUpgradeComponent.Level + 1; - RaiseActionUpgradeEvent(newLevel, actionId.Value); + actionUpgradeComponent.Level = newLevel; + // RaiseActionUpgradeEvent(newLevel, actionId.Value); + + if (!CanUpgrade(newLevel, actionUpgradeComponent.EffectedLevels, out var newActionPrototype) + || !_actions.TryGetActionData(actionId, out var actionComp)) + return null; + + newActionProto ??= newActionPrototype; + DebugTools.AssertNotNull(newActionProto); + + var originalContainer = actionComp.Container; + var originalAttachedEntity = actionComp.AttachedEntity; + + _actionContainer.RemoveAction(actionId.Value, actionComp); + + EntityUid? upgradedActionId = null; + if (originalContainer != null + && TryComp(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(upgradedActionId, out var upgradeComp)) + return null; + + upgradeComp.Level = newLevel; + + // TODO: Preserve ordering of actions + + _entityManager.DeleteEntity(actionId); + + return upgradedActionId.Value; } private void RaiseActionUpgradeEvent(int level, EntityUid actionId) diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs index fc7af4a408..4eaa641eeb 100644 --- a/Content.Shared/Actions/SharedActionsSystem.cs +++ b/Content.Shared/Actions/SharedActionsSystem.cs @@ -691,6 +691,24 @@ public abstract class SharedActionsSystem : EntitySystem } } + /// + /// Grants the provided action from the container to the target entity. If the target entity has no action + /// component, this will give them one. + /// + /// + /// + /// + public void GrantContainedAction(Entity performer, Entity container, EntityUid actionId) + { + if (!Resolve(container, ref container.Comp)) + return; + + performer.Comp ??= EnsureComp(performer); + + if (TryGetActionData(actionId, out var action)) + AddActionDirect(performer, actionId, performer.Comp, action); + } + public IEnumerable<(EntityUid Id, BaseActionComponent Comp)> GetActions(EntityUid holderId, ActionsComponent? actions = null) { if (!Resolve(holderId, ref actions, false)) @@ -723,6 +741,18 @@ public abstract class SharedActionsSystem : EntitySystem } } + /// + /// Removes a single provided action provided by another entity. + /// + public void RemoveProvidedAction(EntityUid performer, EntityUid container, EntityUid actionId, ActionsComponent? comp = null) + { + if (!Resolve(performer, ref comp, false) || !TryGetActionData(actionId, out var action)) + return; + + if (action.Container == container) + RemoveAction(performer, actionId, comp); + } + public void RemoveAction(EntityUid? actionId) { if (actionId == null)