diff --git a/.github/workflows/update-wiki.yml b/.github/workflows/update-wiki.yml deleted file mode 100644 index af7264f1ace..00000000000 --- a/.github/workflows/update-wiki.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: Update Wiki - -on: - workflow_dispatch: - push: - branches: [ master, jsondump ] - paths: - - '.github/workflows/update-wiki.yml' - - 'Content.Shared/Chemistry/**.cs' - - 'Content.Server/Chemistry/**.cs' - - 'Content.Server/GuideGenerator/**.cs' - - 'Resources/Prototypes/Reagents/**.yml' - - 'Resources/Prototypes/Chemistry/**.yml' - - 'Resources/Prototypes/Recipes/Reactions/**.yml' - - 'RobustToolbox/' - -jobs: - update-wiki: - name: Build and Publish JSON blobs to wiki - runs-on: ubuntu-latest - - steps: - - name: Checkout Master - uses: actions/checkout@v3.6.0 - - - name: Setup Submodule - run: | - git submodule update --init --recursive - - - name: Pull Engine Updates - uses: space-wizards/submodule-dependency@v0.1.5 - - - name: Update Engine Submodules - run: | - cd RobustToolbox/ - git submodule update --init --recursive - - - name: Setup .NET Core - uses: actions/setup-dotnet@v3.2.0 - with: - dotnet-version: 7.0.x - - - name: Install Dependencies - run: dotnet restore - - - name: Build Project - run: dotnet build --configuration Release --no-restore /p:WarningsAsErrors=nullable /m - - - name: Generate JSON blobs for prototypes - run: dotnet ./bin/Content.Server/Content.Server.dll --cvar autogen.destination_file=prototypes.json - continue-on-error: true - - - name: Upload chem_prototypes.json to wiki - uses: jtmullen/mediawiki-edit-action@v0.1.1 - with: - wiki_text_file: ./bin/Content.Server/data/chem_prototypes.json - edit_summary: Update chem_prototypes.json via GitHub Actions - page_name: "${{ secrets.WIKI_PAGE_ROOT }}/chem_prototypes.json" - api_url: ${{ secrets.WIKI_ROOT_URL }}/api.php - username: ${{ secrets.WIKI_BOT_USER }} - password: ${{ secrets.WIKI_BOT_PASS }} - - - name: Upload react_prototypes.json to wiki - uses: jtmullen/mediawiki-edit-action@v0.1.1 - with: - wiki_text_file: ./bin/Content.Server/data/react_prototypes.json - edit_summary: Update react_prototypes.json via GitHub Actions - page_name: "${{ secrets.WIKI_PAGE_ROOT }}/react_prototypes.json" - api_url: ${{ secrets.WIKI_ROOT_URL }}/api.php - username: ${{ secrets.WIKI_BOT_USER }} - password: ${{ secrets.WIKI_BOT_PASS }} - diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs index 07b0e6331d1..41db0ad7ceb 100644 --- a/Content.Client/Actions/ActionsSystem.cs +++ b/Content.Client/Actions/ActionsSystem.cs @@ -4,7 +4,6 @@ using JetBrains.Annotations; using Robust.Client.GameObjects; using Robust.Client.Player; -using Robust.Shared.Containers; using Robust.Shared.ContentPack; using Robust.Shared.GameStates; using Robust.Shared.Input.Binding; @@ -28,8 +27,8 @@ public sealed class ActionsSystem : SharedActionsSystem [Dependency] private readonly ISerializationManager _serialization = default!; [Dependency] private readonly MetaDataSystem _metaData = default!; - public event Action? ActionAdded; - public event Action? ActionRemoved; + public event Action? OnActionAdded; + public event Action? OnActionRemoved; public event OnActionReplaced? ActionReplaced; public event Action? ActionsUpdated; public event Action? LinkActions; @@ -37,11 +36,8 @@ public sealed class ActionsSystem : SharedActionsSystem public event Action? ClearAssignments; public event Action>? AssignSlot; - /// - /// Queue of entities with that needs to be updated after - /// handling a state. - /// - private readonly Queue _actionHoldersQueue = new(); + private readonly List _removed = new(); + private readonly List<(EntityUid, BaseActionComponent?)> _added = new(); public override void Initialize() { @@ -49,87 +45,148 @@ public override void Initialize() SubscribeLocalEvent(OnPlayerAttached); SubscribeLocalEvent(OnPlayerDetached); SubscribeLocalEvent(HandleComponentState); + + SubscribeLocalEvent(OnInstantHandleState); + SubscribeLocalEvent(OnEntityTargetHandleState); + SubscribeLocalEvent(OnWorldTargetHandleState); } - public override void Dirty(EntityUid? actionId) + private void OnInstantHandleState(EntityUid uid, InstantActionComponent component, ref ComponentHandleState args) { - var action = GetActionData(actionId); - if (_playerManager.LocalPlayer?.ControlledEntity != action?.AttachedEntity) + if (args.Current is not InstantActionComponentState state) return; - base.Dirty(actionId); - ActionsUpdated?.Invoke(); + BaseHandleState(uid, component, state); } - private void HandleComponentState(EntityUid uid, ActionsComponent component, ref ComponentHandleState args) + private void OnEntityTargetHandleState(EntityUid uid, EntityTargetActionComponent component, ref ComponentHandleState args) { - if (args.Current is not ActionsComponentState state) + if (args.Current is not EntityTargetActionComponentState state) return; - component.Actions.Clear(); - component.Actions.UnionWith(EnsureEntitySet(state.Actions, uid)); + component.Whitelist = state.Whitelist; + component.CanTargetSelf = state.CanTargetSelf; + BaseHandleState(uid, component, state); + } - _actionHoldersQueue.Enqueue(uid); + private void OnWorldTargetHandleState(EntityUid uid, WorldTargetActionComponent component, ref ComponentHandleState args) + { + if (args.Current is not WorldTargetActionComponentState state) + return; + + BaseHandleState(uid, component, state); } - protected override void AddActionInternal(EntityUid holderId, EntityUid actionId, BaseContainer container, ActionsComponent holder) + private void BaseHandleState(EntityUid uid, BaseActionComponent component, BaseActionComponentState state) where T : BaseActionComponent { - // Sometimes the client receives actions from the server, before predicting that newly added components will add - // their own shared actions. Just in case those systems ever decided to directly access action properties (e.g., - // action.Toggled), we will remove duplicates: - if (container.Contains(actionId)) - { - ActionReplaced?.Invoke(actionId); - } - else - { - base.AddActionInternal(holderId, actionId, container, holder); - } + component.Icon = state.Icon; + component.IconOn = state.IconOn; + component.IconColor = state.IconColor; + component.Keywords = new HashSet(state.Keywords); + component.Enabled = state.Enabled; + component.Toggled = state.Toggled; + component.Cooldown = state.Cooldown; + component.UseDelay = state.UseDelay; + component.Charges = state.Charges; + component.Container = EnsureEntity(state.Container, uid); + component.EntityIcon = EnsureEntity(state.EntityIcon, uid); + component.CheckCanInteract = state.CheckCanInteract; + component.ClientExclusive = state.ClientExclusive; + component.Priority = state.Priority; + component.AttachedEntity = EnsureEntity(state.AttachedEntity, uid); + component.AutoPopulate = state.AutoPopulate; + component.Temporary = state.Temporary; + component.ItemIconStyle = state.ItemIconStyle; + component.Sound = state.Sound; + + if (_playerManager.LocalPlayer?.ControlledEntity == component.AttachedEntity) + ActionsUpdated?.Invoke(); } - public override void AddAction(EntityUid holderId, EntityUid actionId, EntityUid? provider, ActionsComponent? holder = null, BaseActionComponent? action = null, bool dirty = true, BaseContainer? actionContainer = null) + protected override void UpdateAction(EntityUid? actionId, BaseActionComponent? action = null) { - if (!Resolve(holderId, ref holder, false)) + if (!ResolveActionData(actionId, ref action)) return; - action ??= GetActionData(actionId); - if (action == null) - { - Log.Warning($"No {nameof(BaseActionComponent)} found on entity {actionId}"); + base.UpdateAction(actionId, action); + if (_playerManager.LocalPlayer?.ControlledEntity != action.AttachedEntity) return; - } - - dirty &= !action.ClientExclusive; - base.AddAction(holderId, actionId, provider, holder, action, dirty, actionContainer); - if (holderId == _playerManager.LocalPlayer?.ControlledEntity) - ActionAdded?.Invoke(actionId); + ActionsUpdated?.Invoke(); } - public override void RemoveAction(EntityUid holderId, EntityUid? actionId, ActionsComponent? comp = null, BaseActionComponent? action = null, bool dirty = true) + private void HandleComponentState(EntityUid uid, ActionsComponent component, ref ComponentHandleState args) { - if (GameTiming.ApplyingState) + if (args.Current is not ActionsComponentState state) return; - if (!Resolve(holderId, ref comp, false)) - return; + _added.Clear(); + _removed.Clear(); + var stateEnts = EnsureEntitySet(state.Actions, uid); + foreach (var act in component.Actions) + { + if (!stateEnts.Contains(act) && !IsClientSide(act)) + _removed.Add(act); + } + component.Actions.ExceptWith(_removed); + + foreach (var actionId in stateEnts) + { + if (!actionId.IsValid()) + continue; - if (actionId == null) + if (!component.Actions.Add(actionId)) + continue; + + TryGetActionData(actionId, out var action); + _added.Add((actionId, action)); + } + + if (_playerManager.LocalPlayer?.ControlledEntity != uid) return; - action ??= GetActionData(actionId); + foreach (var action in _removed) + { + OnActionRemoved?.Invoke(action); + } + + _added.Sort(ActionComparer); + + foreach (var action in _added) + { + OnActionAdded?.Invoke(action.Item1); + } + + ActionsUpdated?.Invoke(); + } + + public static int ActionComparer((EntityUid, BaseActionComponent?) a, (EntityUid, BaseActionComponent?) b) + { + var priorityA = a.Item2?.Priority ?? 0; + var priorityB = b.Item2?.Priority ?? 0; + if (priorityA != priorityB) + return priorityA - priorityB; + + priorityA = a.Item2?.Container?.Id ?? 0; + priorityB = b.Item2?.Container?.Id ?? 0; + return priorityA - priorityB; + } - if (action is { ClientExclusive: false }) + protected override void ActionAdded(EntityUid performer, EntityUid actionId, ActionsComponent comp, + BaseActionComponent action) + { + if (_playerManager.LocalPlayer?.ControlledEntity != performer) return; - dirty &= !action?.ClientExclusive ?? true; - base.RemoveAction(holderId, actionId, comp, action, dirty); + OnActionAdded?.Invoke(actionId); + } - if (_playerManager.LocalPlayer?.ControlledEntity != holderId) + protected override void ActionRemoved(EntityUid performer, EntityUid actionId, ActionsComponent comp, BaseActionComponent action) + { + if (_playerManager.LocalPlayer?.ControlledEntity != performer) return; - if (action == null || action.AutoRemove) - ActionRemoved?.Invoke(actionId.Value); + OnActionRemoved?.Invoke(actionId); } public IEnumerable<(EntityUid Id, BaseActionComponent Comp)> GetClientActions() @@ -180,9 +237,6 @@ public void TriggerAction(EntityUid actionId, BaseActionComponent action) return; } - if (action.Provider != null && Deleted(action.Provider)) - return; - if (action is not InstantActionComponent instantAction) return; @@ -233,8 +287,8 @@ public void LoadActionAssignments(string path, bool userData) var action = _serialization.Read(actionNode, notNullableOverride: true); var actionId = Spawn(null); - AddComp(actionId, action); - AddAction(user, actionId, null); + AddComp(actionId, action); + AddActionDirect(user, actionId); if (map.TryGet("name", out var nameNode)) _metaData.SetEntityName(actionId, nameNode.Value); @@ -254,95 +308,6 @@ public void LoadActionAssignments(string path, bool userData) AssignSlot?.Invoke(assignments); } - public override void Update(float frameTime) - { - base.Update(frameTime); - - if (_actionHoldersQueue.Count == 0) - return; - - var removed = new List(); - var added = new List<(EntityUid Id, BaseActionComponent Comp)>(); - var query = GetEntityQuery(); - var queue = new Queue(_actionHoldersQueue); - _actionHoldersQueue.Clear(); - - while (queue.TryDequeue(out var holderId)) - { - if (!TryGetContainer(holderId, out var container) || container.ExpectedEntities.Count > 0) - { - _actionHoldersQueue.Enqueue(holderId); - continue; - } - - if (!query.TryGetComponent(holderId, out var holder)) - continue; - - removed.Clear(); - added.Clear(); - - foreach (var (act, data) in holder.OldClientActions.ToList()) - { - if (data.ClientExclusive) - continue; - - if (!holder.Actions.Contains(act)) - { - holder.OldClientActions.Remove(act); - if (data.AutoRemove) - removed.Add(act); - } - } - - // Anything that remains is a new action - foreach (var newAct in holder.Actions) - { - if (!TryGetActionData(newAct, out var serverData)) - continue; - - if (!holder.OldClientActions.ContainsKey(newAct)) - added.Add((newAct, serverData)); - - holder.OldClientActions[newAct] = new ActionMetaData(serverData.ClientExclusive, serverData.AutoRemove); - } - - if (_playerManager.LocalPlayer?.ControlledEntity != holderId) - return; - - foreach (var action in removed) - { - ActionRemoved?.Invoke(action); - } - - added.Sort(static (a, b) => - { - if (a.Comp.Priority != b.Comp.Priority) - return a.Comp.Priority - b.Comp.Priority; - - if (a.Comp.Provider != b.Comp.Provider) - { - if (a.Comp.Provider == null) - return -1; - - if (b.Comp.Provider == null) - return 1; - - // uid to int casting... it says "Do NOT use this in content". You can't tell me what to do. - return (int) a.Comp.Provider - (int) b.Comp.Provider; - } - - return 0; - }); - - foreach (var action in added) - { - ActionAdded?.Invoke(action.Item1); - } - - ActionsUpdated?.Invoke(); - } - } - public record struct SlotAssignment(byte Hotbar, byte Slot, EntityUid ActionId); } } diff --git a/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupWindow.xaml b/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupWindow.xaml index eac7e37a86d..311829e8b2b 100644 --- a/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupWindow.xaml +++ b/Content.Client/Administration/UI/AdminRemarks/AdminMessagePopupWindow.xaml @@ -1,13 +1,9 @@ - - - - +